From a12b83ddfbf0f255d8976cf35f262498b11d8fcf Mon Sep 17 00:00:00 2001 From: Stephen Gilbert Date: Fri, 24 Jan 2014 10:14:17 -0500 Subject: [PATCH] VLab Issue #2866 - EDEX and EDEX Common delivery for 14.2.1 Updates to GRIB decoder tables. Changes to some decoder plugins to support data ingest through qpid. Change-Id: Id42e993b1ac76040ad19c970c43036d3f852def0 Former-commit-id: f64ce96da2871e55b40b6fe4ba0e77eaed266103 --- .../opt/db/ddl/ncep/importNcepShapeFile.sh | 18 +- .../base/grid/grib1ParameterConvTable.xml | 271 +- .../base/grib/grids/UkmetHR-Grid1.xml | 35 + .../base/grib/grids/UkmetHR-Grid2.xml | 35 + .../base/grib/grids/UkmetHR-Grid3.xml | 35 + .../base/grib/grids/UkmetHR-Grid4.xml | 35 + .../base/grib/grids/UkmetHR-Grid5.xml | 35 + .../base/grib/grids/UkmetHR-Grid6.xml | 35 + .../base/grib/grids/UkmetHR-Grid7.xml | 35 + .../base/grib/grids/UkmetHR-Grid8.xml | 35 + .../base/grib/grids/UkmetHR-NHemisphere.xml | 35 + .../base/grib/grids/UkmetHR-SHemisphere.xml | 35 + .../base/grib/grids/grid115001.xml | 32 + .../base/grib/grids/grid18060001.xml | 32 + .../base/grib/grids/grid18090001.xml | 32 + .../base/grib/grids/grid2090001.xml | 32 + .../base/grib/grids/grid2091001.xml | 32 + .../base/grib/grids/grid360180001.xml | 32 + .../base/grib/grids/grid3636001.xml | 32 + .../base/grib/grids/grid9050001.xml | 32 + .../base/grib/grids/grid9090001.xml | 32 + .../base/grib/models/gribModels_ECMWF-98.xml | 14 + .../base/grib/models/gribModels_NCEP-7.xml | 97 + .../base/grib/models/gribModels_UKMET-74.xml | 101 +- .../base/grib/tables/7/16/0.0.table | 5 + .../base/grib/tables/7/16/3.2.table | 5 + .../base/grib/tables/7/16/4.1.table | 15 + .../base/grib/tables/7/16/4.2.4.0.table | 10 + .../base/grib/tables/7/16/4.2.4.1.table | 8 + .../base/grib/tables/7/16/4.2.4.2.table | 17 + .../base/grib/tables/7/16/4.2.4.3.table | 12 + .../base/grib/tables/7/16/4.2.4.4.table | 11 + .../base/grib/tables/7/16/4.2.4.5.table | 4 + .../base/grib/tables/7/16/4.2.4.6.table | 13 + .../base/grib/tables/7/16/4.2.4.7.table | 10 + .../base/grib/tables/7/16/4.2.4.8.table | 14 + .../base/grib/tables/7/16/4.2.4.9.table | 7 + .../base/grib/tables/7/16/4.5.table | 48 + .../base/grib/tables/74/0/4.2.2.0.table | 5 + .../base/grib/thinnedModels/UkmetHR-NH.xml | 26 + .../base/grib/thinnedModels/UkmetHR-SH.xml | 26 + .../common_static/base/level/alias/gempak.xml | 4 + .../base/parameter/alias/gempak.xml | 20 +- .../dataplugin/mcidas/McidasMapCoverage.java | 227 +- .../mcidas/McidasSpatialFactory.java | 29 + .../dataplugin/ncscat/NcscatRecord.java | 2 +- .../parameterconversion/PRLibrary.java | 5374 +++--- .../parameterconversion/PSLibrary.java | 830 +- .../NcInventoryDefinitions/NotUsed/QSCT.xml | 13 - .../edex_static/base/ncep/stns/vors.xml | 14146 ++++++++-------- .../plugin/mcidas/decoder/McidasDecoder.java | 6 +- .../edex_static/base/distribution/mosaic.xml | 2 +- .../base/grib/ncgrib/ncgribModelNameMap.xml | 6 +- .../res/spring/ncscat-ingest.xml | 2 +- .../edex_static/base/distribution/ncscat.xml | 1 + .../edex/plugin/ncuair/util/NcUairParser.java | 2140 +-- .../res/spring/ntrans-ingest.xml | 2 +- .../edex_static/base/distribution/ntrans.xml | 1 + .../base/distribution/solarimage.xml | 2 +- .../gov.noaa.nws.ncep.edex.uengine/gemglb.nts | 191 - ncep/gov.noaa.nws.ncep.edex.uengine/last.nts | 7 - .../uengine/tasks/profile/MergeSounding.java | 3368 ++-- 62 files changed, 14762 insertions(+), 12986 deletions(-) mode change 100644 => 100755 edexOsgi/build.edex/opt/db/ddl/ncep/importNcepShapeFile.sh create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid1.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid2.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid3.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid4.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid5.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid6.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid7.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid8.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-NHemisphere.xml create mode 100755 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-SHemisphere.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid115001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18060001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18090001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2090001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2091001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid360180001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid3636001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9050001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9090001.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/0.0.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/3.2.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.1.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.0.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.1.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.2.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.3.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.4.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.5.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.6.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.7.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.8.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.9.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.5.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/74/0/4.2.2.0.table create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-NH.xml create mode 100644 edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-SH.xml delete mode 100644 ncep/gov.noaa.nws.ncep.edex.common/utility/common_static/base/ncep/hold/NcInventoryDefinitions/NotUsed/QSCT.xml delete mode 100644 ncep/gov.noaa.nws.ncep.edex.uengine/gemglb.nts delete mode 100644 ncep/gov.noaa.nws.ncep.edex.uengine/last.nts diff --git a/edexOsgi/build.edex/opt/db/ddl/ncep/importNcepShapeFile.sh b/edexOsgi/build.edex/opt/db/ddl/ncep/importNcepShapeFile.sh old mode 100644 new mode 100755 index 3e08cbb81e..b8e1a9fc4b --- a/edexOsgi/build.edex/opt/db/ddl/ncep/importNcepShapeFile.sh +++ b/edexOsgi/build.edex/opt/db/ddl/ncep/importNcepShapeFile.sh @@ -35,29 +35,31 @@ fi if [ -z $7 ] ; then PGBINDIR='' + PSQLBINDIR='' else PGBINDIR=${7}/bin/ + PSQLBINDIR=/awips2/psql/bin/ fi echo " Importing `basename ${SHAPEFILE}` into ${SCHEMA}.${TABLE} ..." if [ -n "$LOGFILE" ] ; then - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " DELETE FROM public.geometry_columns WHERE f_table_schema = '${SCHEMA}' AND f_table_name = '${TABLE}'; DROP TABLE IF EXISTS ${SCHEMA}.${TABLE} " >> $LOGFILE 2>&1 - ${PGBINDIR}shp2pgsql -s 4326 -I ${SHAPEFILE} ${SCHEMA}.${TABLE} 2>> $LOGFILE | ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -f - >> $LOGFILE 2>&1 - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PGBINDIR}shp2pgsql -s 4326 -g the_geom -I ${SHAPEFILE} ${SCHEMA}.${TABLE} 2>> $LOGFILE | ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -f - >> $LOGFILE 2>&1 + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " SELECT AddGeometryColumn('${SCHEMA}','${TABLE}','the_geom_0','4326',(SELECT type FROM public.geometry_columns WHERE f_table_schema='${SCHEMA}' and f_table_name='${TABLE}' and f_geometry_column='the_geom'),2); UPDATE ${SCHEMA}.${TABLE} SET the_geom_0=ST_Segmentize(the_geom,0.1); CREATE INDEX ${TABLE}_the_geom_0_gist ON ${SCHEMA}.${TABLE} USING gist(the_geom_0); " >> $LOGFILE 2>&1 else - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " DELETE FROM public.geometry_columns WHERE f_table_schema = '${SCHEMA}' AND f_table_name = '${TABLE}'; DROP TABLE IF EXISTS ${SCHEMA}.${TABLE} " - ${PGBINDIR}shp2pgsql -s 4326 -I ${SHAPEFILE} ${SCHEMA}.${TABLE} | ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -f - - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PGBINDIR}shp2pgsql -s 4326 -g the_geom -I ${SHAPEFILE} ${SCHEMA}.${TABLE} | ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -f - + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " SELECT AddGeometryColumn('${SCHEMA}','${TABLE}','the_geom_0','4326',(SELECT type FROM public.geometry_columns WHERE f_table_schema='${SCHEMA}' and f_table_name='${TABLE}' and f_geometry_column='the_geom'),2); UPDATE ${SCHEMA}.${TABLE} SET the_geom_0=ST_Segmentize(the_geom,0.1); CREATE INDEX ${TABLE}_the_geom_0_gist ON ${SCHEMA}.${TABLE} USING gist(the_geom_0); @@ -73,12 +75,12 @@ if [ -n "$SIMPLEVS" ] ; then SUFFIX= for x in $LEV ; do SUFFIX=${SUFFIX}_${x} ; done if [ -n "$LOGFILE" ] ; then - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " SELECT AddGeometryColumn('${SCHEMA}','${TABLE}','the_geom${SUFFIX}','4326',(SELECT type FROM public.geometry_columns WHERE f_table_schema='${SCHEMA}' and f_table_name='${TABLE}' and f_geometry_column='the_geom'),2); UPDATE ${SCHEMA}.${TABLE} SET the_geom${SUFFIX}=ST_Segmentize(ST_Multi(ST_SimplifyPreserveTopology(the_geom,${LEV})),0.1); CREATE INDEX ${TABLE}_the_geom${SUFFIX}_gist ON ${SCHEMA}.${TABLE} USING gist(the_geom${SUFFIX});" >> $LOGFILE 2>&1 else - ${PGBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " + ${PSQLBINDIR}psql -d ncep -U ${PGUSER} -q -p ${PGPORT} -c " SELECT AddGeometryColumn('${SCHEMA}','${TABLE}','the_geom${SUFFIX}','4326',(SELECT type FROM public.geometry_columns WHERE f_table_schema='${SCHEMA}' and f_table_name='${TABLE}' and f_geometry_column='the_geom'),2); UPDATE ${SCHEMA}.${TABLE} SET the_geom${SUFFIX}=ST_Segmentize(ST_Multi(ST_SimplifyPreserveTopology(the_geom,${LEV})),0.1); CREATE INDEX ${TABLE}_the_geom${SUFFIX}_gist ON ${SCHEMA}.${TABLE} USING gist(the_geom${SUFFIX});" diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/common_static/base/grid/grib1ParameterConvTable.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/common_static/base/grid/grib1ParameterConvTable.xml index a8f112f66c..fabbf7ff67 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/common_static/base/grid/grib1ParameterConvTable.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/common_static/base/grid/grib1ParameterConvTable.xml @@ -1,4 +1,4 @@ - +
98
128 @@ -6891,6 +6893,26 @@ 255
+ + +
98
+ 128 + 15 + 0 + 0 + 4 +
+ + +
98
+ 128 + 16 + 0 + 0 + 5 +
+ +
98
128 @@ -8740,5 +8762,252 @@ 10 0 13 +
+ + + + + +
74
+ 3 + 1 + 0 + 3 + 0
+ + +
74
+ 3 + 2 + 0 + 3 + 1 +
+ + +
74
+ 3 + 5 + 0 + 3 + 3 +
+ + +
74
+ 3 + 7 + 0 + 3 + 5 +
+ + +
74
+ 3 + 11 + 0 + 0 + 0 +
+ + +
74
+ 3 + 15 + 0 + 0 + 4 +
+ + +
74
+ 3 + 16 + 0 + 0 + 5 +
+ + +
74
+ 3 + 20 + 0 + 19 + 0 +
+ + +
74
+ 3 + 33 + 0 + 2 + 2 +
+ + +
74
+ 3 + 34 + 0 + 2 + 3 +
+ + +
74
+ 3 + 40 + 0 + 2 + 9 +
+ + +
74
+ 3 + 51 + 0 + 1 + 0 +
+ + +
74
+ 3 + 52 + 0 + 1 + 1 +
+ + +
74
+ 3 + 59 + 0 + 1 + 7 +
+ + +
74
+ 3 + 61 + 0 + 1 + 8 +
+ + +
74
+ 3 + 72 + 0 + 6 + 2 +
+ + +
74
+ 3 + 73 + 0 + 6 + 3 +
+ + +
74
+ 3 + 74 + 0 + 6 + 4 +
+ + +
74
+ 3 + 75 + 0 + 6 + 5 +
+ + +
74
+ 3 + 111 + 0 + 4 + 0 +
+ + +
74
+ 3 + 128 + 0 + 3 + 1 +
+ + +
74
+ 3 + 138 + 255 + 255 + 255 +
+ + +
74
+ 3 + 143 + 0 + 1 + 195 +
+ + +
74
+ 3 + 144 + 2 + 0 + 192 +
+ + +
74
+ 3 + 146 + 0 + 6 + 15 +
+ + +
74
+ 3 + 147 + 0 + 3 + 16 +
+ + +
74
+ 3 + 149 + 0 + 2 + 14 +
+ diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid1.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid1.xml new file mode 100755 index 0000000000..bcdcf61c4c --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid1.xml @@ -0,0 +1,35 @@ + + + + 108162001 + UKMet HiRes tile - Northern Hemisphere + Longitude range 341.25E - 70.416E + 0.279 + 341.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + 89.722 + 70.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid2.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid2.xml new file mode 100755 index 0000000000..d035f4a23e --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid2.xml @@ -0,0 +1,35 @@ + + + + 108162002 + UKMet HiRes tile - Northern Hemisphere + Longitude range 71.25E - 160.416E + 0.279 + 71.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + 89.722 + 160.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid3.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid3.xml new file mode 100755 index 0000000000..e503693fe3 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid3.xml @@ -0,0 +1,35 @@ + + + + 108162003 + UKMet HiRes tile - Northern Hemisphere + Longitude range 161.25E - 250.416E + 0.279 + 161.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + 89.722 + 250.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid4.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid4.xml new file mode 100755 index 0000000000..2f47a455fc --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid4.xml @@ -0,0 +1,35 @@ + + + + 108162004 + UKMet HiRes tile - Northern Hemisphere + Longitude range 251.25E - 340.416E + 0.279 + 251.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + 89.722 + 340.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid5.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid5.xml new file mode 100755 index 0000000000..8776dfab67 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid5.xml @@ -0,0 +1,35 @@ + + + + 108162005 + UKMet HiRes tile - Southern Hemisphere + Longitude range 341.25E - 70.416E + -89.721 + 341.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + -0.278 + 70.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid6.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid6.xml new file mode 100755 index 0000000000..515121aca3 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid6.xml @@ -0,0 +1,35 @@ + + + + 108162006 + UKMet HiRes tile - Southern Hemisphere + Longitude range 71.25E - 160.416E + -89.721 + 71.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + -0.278 + 160.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid7.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid7.xml new file mode 100755 index 0000000000..01461ae493 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid7.xml @@ -0,0 +1,35 @@ + + + + 108162007 + UKMet HiRes tile - Southern Hemisphere + Longitude range 161.25E - 250.416E + -89.721 + 161.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + -0.278 + 250.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid8.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid8.xml new file mode 100755 index 0000000000..2fd6dadf21 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-Grid8.xml @@ -0,0 +1,35 @@ + + + + 108162008 + UKMet HiRes tile - Southern Hemisphere + Longitude range 251.25E - 340.416E + -89.721 + 251.25 + LowerLeft + 108 + 162 + 0.833 + 0.556 + degree + -0.278 + 340.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-NHemisphere.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-NHemisphere.xml new file mode 100755 index 0000000000..b407b250d1 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-NHemisphere.xml @@ -0,0 +1,35 @@ + + + + 864162001 + UKMet HiRes combined - Northern Hemisphere + Longitude range 71.25E - 70.416E + 0.279 + 71.25 + LowerLeft + 864 + 162 + 0.833 + 0.556 + degree + 89.722 + 70.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-SHemisphere.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-SHemisphere.xml new file mode 100755 index 0000000000..f72631f876 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/UkmetHR-SHemisphere.xml @@ -0,0 +1,35 @@ + + + + 864162002 + UKMet HiRes combined - Southern Hemisphere + Longitude range 71.25E - 70.416E + -89.721 + 71.25 + LowerLeft + 864 + 162 + 0.833 + 0.556 + degree + -0.278 + 70.416 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid115001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid115001.xml new file mode 100644 index 0000000000..d0bf065392 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid115001.xml @@ -0,0 +1,32 @@ + + + + 115001 + SWPC IRGF C + 38.0 + 250.0 + LowerLeft + 11 + 5 + 1 + 1 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18060001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18060001.xml new file mode 100644 index 0000000000..b668173ad2 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18060001.xml @@ -0,0 +1,32 @@ + + + + 18060001 + SWPC Enlil C + 59.0 + 1.0 + UpperLeft + 180 + 60 + 2 + 2 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18090001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18090001.xml new file mode 100644 index 0000000000..2319b8d70e --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid18090001.xml @@ -0,0 +1,32 @@ + + + + 18090001 + SWPC XRAY C + 89.0 + 178.0 + UpperLeft + 180 + 90 + 2 + 2 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2090001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2090001.xml new file mode 100644 index 0000000000..8c38353646 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2090001.xml @@ -0,0 +1,32 @@ + + + + 2090001 + SWPC Conductivity C + -90.0 + 180.0 + LowerLeft + 20 + 90 + 18 + 2 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2091001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2091001.xml new file mode 100644 index 0000000000..06c2ae9bba --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid2091001.xml @@ -0,0 +1,32 @@ + + + + 2091001 + SWPC Ion Temperature C + -90.0 + 0.0 + LowerLeft + 20 + 91 + 18 + 2 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid360180001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid360180001.xml new file mode 100644 index 0000000000..ed9134a568 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid360180001.xml @@ -0,0 +1,32 @@ + + + + 360180001 + SWPC SXI C + 90.0 + 0.0 + UpperLeft + 360 + 180 + 1 + 1 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid3636001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid3636001.xml new file mode 100644 index 0000000000..8da1845930 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid3636001.xml @@ -0,0 +1,32 @@ + + + + 3636001 + SWPC SOLAR FLUX C + 87.5 + 185.0 + UpperLeft + 36 + 36 + 10 + 5 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9050001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9050001.xml new file mode 100644 index 0000000000..46b3305503 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9050001.xml @@ -0,0 +1,32 @@ + + + + 9050001 + SWPC Ovation C + 40.0 + 0.0 + LowerLeft + 90 + 50 + 4 + 1 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9090001.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9090001.xml new file mode 100644 index 0000000000..316e7cf0a9 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/grids/grid9090001.xml @@ -0,0 +1,32 @@ + + + + 9090001 + SWPC DRAP20 C + 89.0 + 182.0 + UpperLeft + 90 + 90 + 4 + 2 + degree + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_ECMWF-98.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_ECMWF-98.xml index d2bfc35298..6b54fab4d5 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_ECMWF-98.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_ECMWF-98.xml @@ -11,6 +11,20 @@ + + ecens +
98
+ 0 + 360181001 + + 141 + 142 + 143 + 144 + 145 + +
+ ecmwfP25
98
diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NCEP-7.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NCEP-7.xml index 7e83234f0c..cec4d8e4e3 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NCEP-7.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NCEP-7.xml @@ -2879,6 +2879,7 @@ HPCGuide
7
5 + 197 183 @@ -3131,6 +3132,16 @@
+ + AK-RTMA-HR +
7
+ 4 + 1023 + + 109 + +
+ GFSLAMPTstorm
7
@@ -3184,4 +3195,90 @@ + + + + Conductivity +
7
+ 16 + 2090001 + + 255 + +
+ + SXI +
7
+ 16 + 360180001 + + 255 + +
+ + DRAP20 +
7
+ 16 + 9090001 + + 255 + +
+ + ENLIL +
7
+ 16 + 18060001 + + 255 + +
+ + EPFlux +
7
+ 16 + 3636001 + + 255 + +
+ + SolarFlux +
7
+ 16 + 18090001 + + 255 + +
+ + IonTemperature +
7
+ 16 + 2091001 + + 255 + +
+ + IRGF +
7
+ 16 + 115001 + + 255 + +
+ + Ovation +
7
+ 16 + 9050001 + + 255 + +
+ + +
diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_UKMET-74.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_UKMET-74.xml index c334d4fb3f..af5704c089 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_UKMET-74.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_UKMET-74.xml @@ -5,7 +5,10 @@ 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. --> + See_the_AWIPS_II_Master_Rights_File_("Master_Rights_File.pdf")_for further_licensing_information. +History: +F.Achorn/OPC 10/15/13 Added Ukmet HR tiles 1-8 +--> @@ -182,6 +185,100 @@ - + + + UkmetHR-1 +
74
+ 0 + 108162001 + + 15 + 44 + 45 + +
+ + UkmetHR-2 +
74
+ 0 + 108162002 + + 15 + 44 + 45 + +
+ + + UkmetHR-3 +
74
+ 0 + 108162003 + + 15 + 44 + 45 + +
+ + + UkmetHR-4 +
74
+ 0 + 108162004 + + 15 + 44 + 45 + +
+ + + UkmetHR-5 +
74
+ 0 + 108162005 + + 15 + 44 + 45 + +
+ + + UkmetHR-6 +
74
+ 0 + 108162006 + + 15 + 44 + 45 + +
+ + + UkmetHR-7 +
74
+ 0 + 108162007 + + 15 + 44 + 45 + +
+ + + UkmetHR-8 +
74
+ 0 + 108162008 + + 15 + 44 + 45 + +
diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/0.0.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/0.0.table new file mode 100644 index 0000000000..5a61b54c3a --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/0.0.table @@ -0,0 +1,5 @@ +#Code Table 0.0: Discipline of processed data in the GRIB message, number of GRIB Master table +3:3:Satellite remote sensing products +4:4:Space Weather Products +# 192-254 Reserved for local use +255:255:Missing diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/3.2.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/3.2.table new file mode 100644 index 0000000000..3b2a4a9ca3 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/3.2.table @@ -0,0 +1,5 @@ +# CODE TABLE 3.2, Shape of the reference system +10:10:Earth model assumed WGS84 with corrected geomagnetic coordinates (latitude and longitude) defined by Gustafsson et al., 1992 +11:11:Sun assumed spherical with radius = 695,990,000 m (Allen, C.W., 1976 Astrophysical Quantities (3rd Ed.; London: Athlone) and Stonyhurst latitude and longitude system with origin at the intersection of the solar central meridian (as seen from Earth) and the solar equator (Thompson, W, Coordinate systems for solar image data, A&A 449, 791–803 (2006)) +# 192- 254 Reserved for local use +255:255:Missing diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.1.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.1.table new file mode 100644 index 0000000000..f571386795 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.1.table @@ -0,0 +1,15 @@ +# CODE TABLE 4.1, Parameter category by product discipline +0:0:Temperature +1:1:Momentum +2:2:Charged particle mass and number +3:3:Electric and magnetic fields +4:4:Energetic particles +5:5:Waves +6:6:Solar electromagnetic emissions +7:7:Terrestrial electromagnetic emissions +8:8:Imagery +9:9:Ion-neutral coupling +#10-191 Reserved +#192-254 Reserved for Local Use +255:255:Missing + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.0.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.0.table new file mode 100644 index 0000000000..eb9b080aa0 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.0.table @@ -0,0 +1,10 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 0: Temperature +0:0:Temperature:K:TMPSWP +1:1:Electron Temperature:K:ELECTMP +2:2:Proton Temperature:K:PROTTMP +3:3:Ion Temperature:K:IONTMP +4:4:Parallel Temperature:K:PRATMP +5:5:Perpendicular Temperature:K:PRPTMP +# 6-191 Reserved +# 192-254 Reserved for local use +255:255:Missing \ No newline at end of file diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.1.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.1.table new file mode 100644 index 0000000000..bbb309452f --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.1.table @@ -0,0 +1,8 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 1: Momentum +0:0:Velocity Magnitude (Speed):m*s^1:SPEED +1:1:1st Vector Component of Velocity (Coordinate system dependent):m*s^1:VEL1 +2:2:2nd Vector Component of Velocity (Coordinate system dependent):m*s^1:VEL2 +3:3:3rd Vector Component of Velocity (Coordinate system dependent):m*s^1:VEL3 +# 4-191 Reserved +# 192-254 Reserved for local use +255:255:Missing \ No newline at end of file diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.2.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.2.table new file mode 100644 index 0000000000..02af676f2a --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.2.table @@ -0,0 +1,17 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 2: Charged Particle Mass and Number +0:0:Particle Number Density:m^3:PLSMDEN +1:1:Electron Density:m^3:ELCDEN +2:2:Proton Density:m^3:PROTDEN +3:3:Ion Density:m^3:IONDEN +4:4:Vertical Electron Content:m^2:VTEC +5:5:HF Absorption Frequency:Hz:ABSFRQ +6:6:HF Absorption:dB:ABSRB +7:7:Spread F:m:SPRDF +8:8:h'F:m:HPRIMF +9:9:Critical Frequency:Hz:CRTFRQ +10:10:Scintillation:Numeric:SCINT +# 11-191 Reserved +# 192-254 Reserved for local use +255:255:Missing + + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.3.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.3.table new file mode 100644 index 0000000000..af18377221 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.3.table @@ -0,0 +1,12 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 3: Electric and Magnetic Fields +0:0:Magnetic Field Magnitude:T:BTOT +1:1:1st Vector Component of Magnetic Field:T:BVEC1 +2:2:2nd Vector Component of Magnetic Field:T:BVEC2 +3:3:3rd Vector Component of Magnetic Field:T:BVEC3 +4:4:Electric Field Magnitude:V*m^1:ETOT +5:5:1st Vector Component of Electric Field:V*m^1:EVEC1 +6:6:2nd Vector Component of Electric Field:V*m^1:EVEC2 +7:7:3rd Vector Component of Electric Field:V*m^1:EVEC3 +# 8-191 Reserved +# 192-254 Reserved for local use +255:255:Missing \ No newline at end of file diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.4.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.4.table new file mode 100644 index 0000000000..c9a64da52d --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.4.table @@ -0,0 +1,11 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 4: Energetic Particles +0:0:Proton Flux (Differential):(m^2*s*sr*eV)^1:DIFPFLUX +1:1:Proton Flux (Integral):(m^2*s*sr)^1:INTPFLUX +2:2:Electron Flux (Differential):(m^2*s*sr*eV)^1:DIFEFLUX +3:3:Electron Flux (Integral):(m^2*s*sr)^1:INTEFLUX +4:4:Heavy Ion Flux (Differential):((m^2*s*sr*eV)/nuc)^1:DIFIFLUX +5:5:Heavy Ion Flux (iIntegral):(m^2*s*sr)^1:INTIFLUX +6:6:Cosmic Ray Neutron Flux:h^1:NTRNFLUX +# 7-191 Reserved +# 192-254 Reserved for local use +255:255:Missing \ No newline at end of file diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.5.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.5.table new file mode 100644 index 0000000000..7a8a4dab99 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.5.table @@ -0,0 +1,4 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 5: Waves +# 0-191 Reserved +# 192-254 Reserved for local use +255:255:Missing diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.6.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.6.table new file mode 100644 index 0000000000..d4620a017f --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.6.table @@ -0,0 +1,13 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 6: Solar Electromagnetic Emissions +0:0:Integrated Solar Irradiance:W*m^2:TSI +1:1:Solar X-ray Flux (XRS Long):W*m^2:XLONG +2:2:Solar X-ray Flux (XRS Short):W*m^2:XSHRT +3:3:Solar EUV Irradiance:W*m^2:EUVIRR +4:4:Solar Spectral Irradiance:W*m^2*n*m^1:SPECIRR +5:5:F10.7:W*m^2*H*z^1:F107 +6:6:Solar Radio Emissions:W*m^2*Hz^1:SOLRF +# 7-191 Reserved +# 192-254 Reserved for local use +255:255:Missing + + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.7.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.7.table new file mode 100644 index 0000000000..639976511c --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.7.table @@ -0,0 +1,10 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 7: Terrestrial electromagnetic emissions +0:0:Limb Intensity:J*m^2*s^1:LMBINT +1:1:Disk Intensity:j*m^2*s^1:DSKINT +2:2:Disk Intensity Day:J*m^2*s^1:DSKDAY +3:3:Disk Intensity Night:J*m^2*s^1:DSKNGT +# 4-191 Reserved +# 192-254 Reserved for local use +255:255:Missing + + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.8.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.8.table new file mode 100644 index 0000000000..d913d9e458 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.8.table @@ -0,0 +1,14 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 8: Imagery +0:0:X-Ray Radiance:W*s*r^1*m^2:XRAYRAD +1:1:EUV Radiance:W*s*r^1*m^2:EUVRAD +2:2:H-Alpha Radiance:W*s*r^1*m^2:HARAD +3:3:White Light Radiance:W*s*r^1*m^2:WHTRAD +4:4:CaII-K Radiance:W*s*r^1*m^2:CAIIRAD +5:5:White Light Coronagraph Radiance:W*s*r^1*m^2:WHTCOR +6:6:Heliospheric Radiance:W*s*r^1*m^2:HELCOR +7:7:Thematic Mask:Numeric:MASK +# 8-191 Reserved +# 192-254 Reserved for local use +255:255:Missing + + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.9.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.9.table new file mode 100644 index 0000000000..4e0e86035c --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.2.4.9.table @@ -0,0 +1,7 @@ +# Product Discipline 4: Space Weather Products, Parameter Category 9: Ion-Neutral Coupling +0:0:Pedersen Conductivity:S*m^1:SIGPED +1:1:Hall Conductivity:S*m^1:SIGHAL +2:2:Parallel Conductivity:S*m^1:SIGPAR +# 3-191 Reserved +# 192-254 Reserved for local use +255:255:Missing \ No newline at end of file diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.5.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.5.table new file mode 100644 index 0000000000..941066c6f5 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/7/16/4.5.table @@ -0,0 +1,48 @@ +# Code:table:4.5: Fixed surface types and units +170:170:Ionospheric D-region level::IDRL +171:171:Ionospheric E-region level::IERL +172:172:Ionospheric F1-region level::IF1RL +173:173:Ionospheric F2-region level::IF2RL +174:174:Specified radius from the center of the Sun:m:SRCS +175:175:Solar photosphere::SP +#192-254 Reserved for local use +200:200:Entire Atmosphere::EATM:EA +201:201:Entire Ocean::EOCN +204:204:Highest Tropospheric Freezing Level::HTFL +206:206:Grid Scale Cloud Bottom Level::GCBL +207:207:Grid Scale Cloud Top Level::GCTL +209:209:Boundary Layer Cloud Bottom Level::BCBL +210:210:Boundary Layer Cloud Top Level::BCTL +211:211:Boundary Layer Cloud Layer::BCY +212:212:Low Cloud Bottom Level::LCBL +213:213:Low Cloud Top Level::LCTL +214:214:Low Cloud Layer::LCY +215:215:Cloud Ceiling::CLG +220:220:Planetary Boundary Layer:: BLD +221:221:Layer Between Two Hybrid Levels::LBTHL +222:222:Middle Cloud Bottom Level::MCBL +223:223:Middle Cloud Top Level::MCTL +224:224:Middle Cloud Layer::MCY +232:232:High Cloud Bottom Level::HCBL +233:233:High Cloud Top Level::HCTL +234:234:High Cloud Layer::HCL +235:235:Ocean Isotherm Level:1/10 ℃:OITL +236:236:Layer Between Two Depths Below Ocean Surface::OLYR +237:237:Bottom of Ocean Mixed Layer (m):m:OBML +238:238:Bottom of Ocean Isothermal Layer:m:OBIL +239:239:Layer Ocean Surface and 26C Ocean Isothermal Level::LOS +240:240:Ocean Mixed Layer::OML +241:241:Ordered Sequence of Data::OSD +242:242:Convective Cloud Bottom Level::CCBL +243:243:Convective Cloud Top Level::CCTL +244:244:Convective Cloud::CCY +245:245:Lowest Level of the Wet Bulb Zero::WBZ +246:246:Maximum Equivalent Potential Temperature level::MTHE:MEPT +247:247:Equilibrium Level::EHLT:EL +248:248:Shallow Convective Cloud Bottom Level::SCBL +249:249:Shallow Convective Cloud Top Level::SCTL +251:251:Deep Convective Cloud Bottom Level::DCBL +252:252:Deep Convective Cloud Top Level::DCTL +253:253:Lowest Bottom Level of Supercooled Liquid Water Layer::LSCLW +254:254:Highest Top Level of Supercooled Liquid Water Layer::HSCLW +255:255:Missing diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/74/0/4.2.2.0.table b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/74/0/4.2.2.0.table new file mode 100644 index 0000000000..65f6dfdb15 --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/tables/74/0/4.2.2.0.table @@ -0,0 +1,5 @@ +# Product Discipline 2: Land surface products, Parameter Category 0: Vegetation/Biomass +#192-254 Reserved for local use +192:192:Volumetric soil moisture content:Proportion:SOILW +255:255:Missing + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-NH.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-NH.xml new file mode 100644 index 0000000000..5325335d6b --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-NH.xml @@ -0,0 +1,26 @@ + + + + + UkmetHR-NorthernHemisphere + 864162001 + UkmetHR-2:UkmetHR-3:UkmetHR-4:UkmetHR-1 + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-SH.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-SH.xml new file mode 100644 index 0000000000..6503a56ffa --- /dev/null +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/thinnedModels/UkmetHR-SH.xml @@ -0,0 +1,26 @@ + + + + + UkmetHR-SouthernHemisphere + 864162002 + UkmetHR-6:UkmetHR-7:UkmetHR-8:UkmetHR-5 + diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.level/utility/common_static/base/level/alias/gempak.xml b/edexOsgi/com.raytheon.uf.common.dataplugin.level/utility/common_static/base/level/alias/gempak.xml index 50cca22d39..8059f7a35d 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.level/utility/common_static/base/level/alias/gempak.xml +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.level/utility/common_static/base/level/alias/gempak.xml @@ -137,4 +137,8 @@ GCBL SCBL SCTL + SPXX + IDRL + SRCS + NTAT \ No newline at end of file diff --git a/edexOsgi/com.raytheon.uf.common.parameter/utility/common_static/base/parameter/alias/gempak.xml b/edexOsgi/com.raytheon.uf.common.parameter/utility/common_static/base/parameter/alias/gempak.xml index e8dcce7bf6..b00caba142 100644 --- a/edexOsgi/com.raytheon.uf.common.parameter/utility/common_static/base/parameter/alias/gempak.xml +++ b/edexOsgi/com.raytheon.uf.common.parameter/utility/common_static/base/parameter/alias/gempak.xml @@ -91,7 +91,7 @@ WXTP18 WXTP24 WXTP48 - CINH + CINS CLWMR ACPCP C01M @@ -294,15 +294,15 @@ TMPK24 TMPK48 TMPKA - CLD - CLD01 - CLD03 - CLD06 - CLD09 - CLD12 - CLD18 - CLD24 - CLD48 + TCLD + TCLD01 + TCLD03 + TCLD06 + TCLD09 + TCLD12 + TCLD18 + TCLD24 + TCLD48 TSTM TOZO APCP diff --git a/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasMapCoverage.java b/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasMapCoverage.java index a1ca872051..ca528b7dbd 100644 --- a/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasMapCoverage.java +++ b/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasMapCoverage.java @@ -14,8 +14,8 @@ * 12/2009 144 T. Lee Migrated to TO11D6 * 01/2010 201 M. Li Split into dataplugin project * 05/2010 144 L. Lin Migration to TO11DR11. + * 11/2013 1066 G. Hull call constructCRSfromWKT * Nov 14, 2013 2393 bclement added getGridGeometry() - * * */ @@ -24,8 +24,6 @@ package gov.noaa.nws.ncep.common.dataplugin.mcidas; import gov.noaa.nws.ncep.common.tools.IDecoderConstantsN; import java.awt.geom.Rectangle2D; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import javax.persistence.Column; import javax.persistence.Entity; @@ -69,16 +67,18 @@ import com.vividsolutions.jts.geom.Polygon; @Cache(usage = CacheConcurrencyStrategy.TRANSACTIONAL) @XmlAccessorType(XmlAccessType.NONE) @DynamicSerialize -public class McidasMapCoverage extends PersistableDataObject implements ISpatialObject { +public class McidasMapCoverage extends PersistableDataObject implements + ISpatialObject { private static final long serialVersionUID = 1; + @Id private int pid; /** - * The projection of the map coverage 1 = Mercator, 3 = Lambert Conformal or TANC - * 5 = Polar Stereographic - * 7585 = native satellite navigation e.g. GVAR, ... + * The projection of the map coverage 1 = Mercator, 3 = Lambert Conformal or + * TANC 5 = Polar Stereographic 7585 = native satellite navigation e.g. + * GVAR, ... */ @Column @XmlAttribute @@ -96,7 +96,7 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial @XmlAttribute @DynamicSerializeElement private Integer ny; - + /** The pixel resolution of the image */ @Column @XmlAttribute @@ -116,10 +116,11 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial private Float clon; /** - * The standard latitude 1. For the Lambert Conformal projection this is the latitude - * of the proection cone intersects the earth. For the Polar Stereographic this is the - * latitude at which projection plan intersects the earth. For Mercator this is the - * latitude at which the Mercator projection cylinder intersects the earth. + * The standard latitude 1. For the Lambert Conformal projection this is the + * latitude of the proection cone intersects the earth. For the Polar + * Stereographic this is the latitude at which projection plan intersects + * the earth. For Mercator this is the latitude at which the Mercator + * projection cylinder intersects the earth. */ @Column @XmlAttribute @@ -127,14 +128,14 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial private Float stdlat1; /** - * The standard latitude 2 is the second latitude of a secant cone which intersects the - * earth for the Lambert Conformal projection. + * The standard latitude 2 is the second latitude of a secant cone which + * intersects the earth for the Lambert Conformal projection. */ @Column @XmlAttribute @DynamicSerializeElement private Float stdlat2; - + /** The latitude of the lower-left corner */ @Column @XmlAttribute @@ -158,31 +159,31 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial @XmlAttribute @DynamicSerializeElement private Float urlon; - + /** image element coordinate of area line 0, element 0 */ @Column @XmlAttribute @DynamicSerializeElement private int upperLeftElement; - + /** image line coordinate of area line 0, element 0 */ @Column @XmlAttribute @DynamicSerializeElement private int upperLeftLine; - + /** element resolution */ @Column @XmlAttribute @DynamicSerializeElement private int elementRes; - + /** line resolution */ @Column @XmlAttribute @DynamicSerializeElement private int lineRes; - + @Column(length = 5120) @XmlAttribute @DynamicSerializeElement @@ -245,9 +246,10 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial * @param geometry * The geometry */ - public McidasMapCoverage(Integer projection, Integer nx, Integer ny, Float dx, - Float dy, Float clon, Float stdlat1, Float stdlat2, Float lllat, Float lllon, - Float urlat,Float urlon, CoordinateReferenceSystem crs, Geometry geometry) { + public McidasMapCoverage(Integer projection, Integer nx, Integer ny, + Float dx, Float dy, Float clon, Float stdlat1, Float stdlat2, + Float lllat, Float lllon, Float urlat, Float urlon, + CoordinateReferenceSystem crs, Geometry geometry) { this.projection = projection; this.nx = nx; this.ny = ny; @@ -274,19 +276,27 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial * Constructs a new SatMapCoverage Object for native satellite navigation * * @param mapProjection - * @param nx The number of horizontal scan lines - * @param ny The number vertical scan lines - * @param reflon Reference Longitude - * @param upperLeftElement image element coordinate of area line 0, element 0 - * @param upperLeftLine image line coordinate of area line 0, element 0 - * @param xres Element resolution - * @param yres Line resolution - * @param crs The coordinate reference system + * @param nx + * The number of horizontal scan lines + * @param ny + * The number vertical scan lines + * @param reflon + * Reference Longitude + * @param upperLeftElement + * image element coordinate of area line 0, element 0 + * @param upperLeftLine + * image line coordinate of area line 0, element 0 + * @param xres + * Element resolution + * @param yres + * Line resolution + * @param crs + * The coordinate reference system * @param geometry */ public McidasMapCoverage(Integer projection, Integer nx, Integer ny, - Float reflon, int upperLeftElement, int upperLeftLine, int xres, - int yres, ProjectedCRS crs, Geometry geometry) { + Float reflon, int upperLeftElement, int upperLeftLine, int xres, + int yres, ProjectedCRS crs, Geometry geometry) { this.projection = projection; this.nx = nx; this.ny = ny; @@ -307,10 +317,10 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial this.crsWKT = crsObject.toWKT(); this.location = (Polygon) geometry; pid = this.hashCode(); - } + } @Override - public int hashCode() { + public int hashCode() { HashCodeBuilder hashBuilder = new HashCodeBuilder(); hashBuilder.append(projection); hashBuilder.append(nx); @@ -342,39 +352,9 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial if (crsObject == null) { try { crsObject = CRS.parseWKT(crsWKT); - // ReferencingFactoryFinder.getCRSFactory(null).createFromWKT(crsWKT); } catch (Exception e) { - /* - * parseWKT() doesn't recognize PROJCS PARAMETERS whose value is a "String" (it - * assumes all PARAMETER values are doubles.) - * If this crsWKT is a MCIDAS NAV, use McidasSpatialFactory instead. - */ - //e.printStackTrace(); - Pattern p = Pattern.compile("PROJCS\\[\"MCIDAS\\sAREA\\s(.*)\""); - Matcher m = p.matcher(crsWKT); - m.find(); - - if ( m.groupCount() == 1 ) { - String type = m.group(1); - //System.out.println("FOUND PROJCS:"+m.group(0)+":"+type); - p = Pattern.compile("\\[\"NAV_BLOCK_BASE64\",\\s\"(.*)\"\\]"); - m = p.matcher(crsWKT); - boolean found = m.find(); - - //System.out.println(m.group()); - //System.out.println(m.groupCount()+m.group(1)); - if ( found ) { - String navBlock = m.group(1); - crsObject = McidasSpatialFactory.getInstance().constructCRS(type, navBlock); - } - else { - crsObject = null; - } - } - else { - crsObject = null; - } - + crsObject = McidasSpatialFactory.getInstance() + .constructCRSfromWKT(crsWKT); } } return crsObject; @@ -466,7 +446,7 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial public void setStdlat1(Float stdlat1) { this.stdlat1 = stdlat1; } - + public Float getStdlat2() { return stdlat2; } @@ -541,73 +521,78 @@ public class McidasMapCoverage extends PersistableDataObject implements ISpatial this.ny = ny; } - /** - * @return the upperLeftElement - */ - public int getUpperLeftElement() { - return upperLeftElement; - } + * @return the upperLeftElement + */ + public int getUpperLeftElement() { + return upperLeftElement; + } - /** - * @param upperLeftElement the upperLeftElement to set - */ - public void setUpperLeftElement(int upperLeftElement) { - this.upperLeftElement = upperLeftElement; - } + /** + * @param upperLeftElement + * the upperLeftElement to set + */ + public void setUpperLeftElement(int upperLeftElement) { + this.upperLeftElement = upperLeftElement; + } - /** - * @return the upperLeftLine - */ - public int getUpperLeftLine() { - return upperLeftLine; - } + /** + * @return the upperLeftLine + */ + public int getUpperLeftLine() { + return upperLeftLine; + } - /** - * @param upperLeftLine the upperLeftLine to set - */ - public void setUpperLeftLine(int upperLeftLine) { - this.upperLeftLine = upperLeftLine; - } + /** + * @param upperLeftLine + * the upperLeftLine to set + */ + public void setUpperLeftLine(int upperLeftLine) { + this.upperLeftLine = upperLeftLine; + } - /** - * @return the elementRes - */ - public int getElementRes() { - return elementRes; - } + /** + * @return the elementRes + */ + public int getElementRes() { + return elementRes; + } - /** - * @param elementRes the elementRes to set - */ - public void setElementRes(int elementRes) { - this.elementRes = elementRes; - } + /** + * @param elementRes + * the elementRes to set + */ + public void setElementRes(int elementRes) { + this.elementRes = elementRes; + } - /** - * @return the lineRes - */ - public int getLineRes() { - return lineRes; - } + /** + * @return the lineRes + */ + public int getLineRes() { + return lineRes; + } - /** - * @param lineRes the lineRes to set - */ - public void setLineRes(int lineRes) { - this.lineRes = lineRes; - } + /** + * @param lineRes + * the lineRes to set + */ + public void setLineRes(int lineRes) { + this.lineRes = lineRes; + } - public String getCrsWKT() { + public String getCrsWKT() { return crsWKT; } public void setCrsWKT(String crsWKT) { - //TODO new 2.6 version of geotools adds \r\n to long String parameters in WKT format - // this temp hack removes the extraneous characters, but we may want to investigate - // using a specific formatter to keep this consistent and in our control + // TODO new 2.6 version of geotools adds \r\n to long String parameters + // in WKT format + // this temp hack removes the extraneous characters, but we may want to + // investigate + // using a specific formatter to keep this consistent and in our control this.crsWKT = crsWKT.replaceAll("\r\n", ""); - //this.crsWKT = crsWKT; + // this.crsWKT = crsWKT; } public Polygon getLocation() { diff --git a/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasSpatialFactory.java b/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasSpatialFactory.java index dd1e676cce..91da6527a8 100644 --- a/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasSpatialFactory.java +++ b/ncep/gov.noaa.nws.ncep.common.dataplugin.mcidas/src/gov/noaa/nws/ncep/common/dataplugin/mcidas/McidasSpatialFactory.java @@ -9,6 +9,7 @@ * ------------ ---------- ----------- -------------------------- * 10/2009 144 T. Lee Created * 12/2009 144 T. Lee Migrated to TO11D6 + * 11/2013 1066 G. Hull constructCRSfromWKT (from McidasMapCoverage) * * * @@ -18,6 +19,9 @@ package gov.noaa.nws.ncep.common.dataplugin.mcidas; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import gov.noaa.nws.ncep.common.dataplugin.mcidas.dao.McidasMapCoverageDao; import org.apache.commons.codec.binary.Base64; @@ -28,6 +32,7 @@ import org.geotools.referencing.operation.DefaultMathTransformFactory; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.FactoryException; import org.opengis.referencing.NoSuchIdentifierException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.ProjectedCRS; import org.opengis.referencing.operation.MathTransform; @@ -408,6 +413,30 @@ public class McidasSpatialFactory { return new String(coded); } + public ProjectedCRS constructCRSfromWKT( String crsWKT) { + Pattern p = Pattern.compile("PROJCS\\[\"MCIDAS\\sAREA\\s(.*)\""); + Matcher m = p.matcher(crsWKT); + m.find(); + ProjectedCRS crsObject=null; + + if ( m.groupCount() == 1 ) { + String type = m.group(1); + //System.out.println("FOUND PROJCS:"+m.group(0)+":"+type); + p = Pattern.compile("\\[\"NAV_BLOCK_BASE64\",\\s\"(.*)\"\\]"); + m = p.matcher(crsWKT); + boolean found = m.find(); + + //System.out.println(m.group()); + //System.out.println(m.groupCount()+m.group(1)); + if ( found ) { + String navBlock = m.group(1); + crsObject = McidasSpatialFactory.getInstance().constructCRS(type, navBlock); + } + } + + return crsObject; + } + public ProjectedCRS constructCRS(String type, String encoded) { ParameterValueGroup pvg = null; diff --git a/ncep/gov.noaa.nws.ncep.common.dataplugin.ncscat/src/gov/noaa/nws/ncep/common/dataplugin/ncscat/NcscatRecord.java b/ncep/gov.noaa.nws.ncep.common.dataplugin.ncscat/src/gov/noaa/nws/ncep/common/dataplugin/ncscat/NcscatRecord.java index 031199a14b..348cb46bdb 100644 --- a/ncep/gov.noaa.nws.ncep.common.dataplugin.ncscat/src/gov/noaa/nws/ncep/common/dataplugin/ncscat/NcscatRecord.java +++ b/ncep/gov.noaa.nws.ncep.common.dataplugin.ncscat/src/gov/noaa/nws/ncep/common/dataplugin/ncscat/NcscatRecord.java @@ -167,6 +167,6 @@ public class NcscatRecord extends PersistablePluginDataObject { @Override public String getPluginName() { - return "ncsat"; + return "ncscat"; } } diff --git a/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PRLibrary.java b/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PRLibrary.java index 40646838a1..34e3392658 100644 --- a/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PRLibrary.java +++ b/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PRLibrary.java @@ -8,13 +8,9 @@ */ package gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion; -import java.util.Arrays; - import gov.noaa.nws.ncep.edex.common.metparameters.Amount; -import gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion.GempakConstants; -import gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion.NcUnits; -//import com.raytheon.uf.viz.core.exception.VizException; +import java.util.Arrays; import javax.measure.quantity.Temperature; import javax.measure.quantity.VolumetricDensity; @@ -22,2555 +18,2885 @@ import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; -public final class PRLibrary -{ +import org.opengis.coverage.grid.InvalidRangeException; +//import com.raytheon.uf.viz.core.exception.VizException; - /** - * No-arguments constructor - */ - public PRLibrary(){ - - } - /** - * Computes altimeter from the station pressure and elevation - * @param pres - pressure at the station - * @param selv - elevation of the station - * @return the computed altimeter in Inches - * @throws NullPointerException - * - */ - public static final Amount prAltp( Amount pres, Amount selv) throws InvalidValueException, NullPointerException{ - //System.out.println("From prAltp:"); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" selv = " + selv.doubleValue()); - - if ( !checkNullOrInvalidValue( pres ) || !checkNullOrInvalidValue( selv ) ) - return new Amount ( NonSI.INCH_OF_MERCURY); - - selv = checkAndConvertInputAmountToExpectedUnits(selv, SI.METER ); - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - double seaLevelTempInKelvin = GempakConstants.TMCK + 15; - double hgtk = selv.getUnit().getConverterTo( SI.KILOMETER ).convert( selv.doubleValue() ) ; - double exponent = - ( GempakConstants.GRAVTY / ( GempakConstants.GAMUSD * GempakConstants.RDGAS ) * 1000 ); -// double base = pres * ( 1.0f - ( hgtk * GempakConstants.GAMUSD / seaLevelTempInKelvin ) ); -// float altm = (float) Math.pow( base, Math.exp( exponent ) ); -// return prAlti( pres * altm ); - double base = ( 1.0 - ( hgtk * GempakConstants.GAMUSD / seaLevelTempInKelvin ) ); - double altm = Math.pow( base, exponent ); - double altp = pres.doubleValue() * altm; - return ( new Amount ( altp, NonSI.INCH_OF_MERCURY ) ); - } - - +public final class PRLibrary { - - /*** - * Computes the Ceiling converted to Mean Sea Level in hundreds of feet - * @param ceil - Ceiling in hundreds of feet - * @param selv - Station elevation in meters - * @return The ceiling converted to Mean Sea Level in hundreds of feet - * @throws NullPointerException - * - */ - public static final Amount prCmsl( Amount ceil, Amount selv) throws InvalidValueException, NullPointerException{ - //System.out.println("From prCmsl:"); - //System.out.println(" ceil = " + ceil.doubleValue()); - //System.out.println(" selv = " + selv.doubleValue()); - /*Sanity check*/ -// checkNullOrInvalidValue( ceil ); -// checkNullOrInvalidValue( selv ); - if ( !checkNullOrInvalidValue( ceil ) || !checkNullOrInvalidValue( selv ) ) - return new Amount ( NcUnits.HUNDREDS_OF_FEET); - - selv = checkAndConvertInputAmountToExpectedUnits(selv, NcUnits.HUNDREDS_OF_FEET); - ceil = checkAndConvertInputAmountToExpectedUnits(ceil, NcUnits.HUNDREDS_OF_FEET); - return ( new Amount ( selv.doubleValue() + ceil.doubleValue() , NcUnits.HUNDREDS_OF_FEET ) ); - } - - /** - * Computes the wind direction in degrees from the input u and v components of velocity, - * both of which must be in the same units (either meters/second or knots) - * @param uX - U component of velocity - * @param vX- V component of velocity - * @return The wind direction in degrees - * @throws NullPointerException - * - */ - public static final Amount prDrct ( Amount uX, Amount vX) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDrct:"); - //System.out.println(" uX = " + uX.doubleValue()); - //System.out.println(" vX = " + vX.doubleValue()); - -// checkNullOrInvalidValue(uX); -// checkNullOrInvalidValue(vX); - if ( !checkNullOrInvalidValue( uX ) || !checkNullOrInvalidValue( vX ) ) - return new Amount ( NonSI.DEGREE_ANGLE); - - double vXVal = Double.NaN; - double uXVal = Double.NaN; - if ( uX.getUnit() != vX.getUnit() && uX.getUnit().isCompatible(vX.getUnit() )) { - vXVal = vX.getUnit().getConverterTo(uX.getUnit() ).convert(vX.doubleValue() ); - vX = new Amount ( vXVal, uX.getUnit() ); - } - uXVal = uX.doubleValue(); - double prDrct = 0.0; + /** + * No-arguments constructor + */ + public PRLibrary() { - if ( ( uXVal != 0 ) || ( vXVal != 0 ) ){ - prDrct = Math.atan2(-uXVal, -vXVal) * GempakConstants.RTD; - if ( prDrct <= 0 ) - prDrct += 360; - } - return ( new Amount ( prDrct , NonSI.DEGREE_ANGLE )); + } - } - - /** - * Computes the density of dry air given the pressure (in mb) - * and temperature (in Celsius) - * @param pres - Pressure in millibars - * @param tmpc - Temperature in Celsius - * @return Density of dry air in kg/(m**3) - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prDden ( Amount pres, Amount tmpc ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDden:"); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - - double prdden = GempakConstants.RMISSD; - /*Check for bad data*/ -// checkNullOrInvalidValue(pres); -// checkNullOrInvalidValue(tmpc); + /** + * Computes altimeter from the station pressure and elevation + * + * @param pres + * - pressure at the station + * @param selv + * - elevation of the station + * @return the computed altimeter in Inches + * @throws NullPointerException + * + */ + public static final Amount prAltp(Amount pres, Amount selv) + throws InvalidValueException, NullPointerException { + // System.out.println("From prAltp:"); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println(" selv = " + selv.doubleValue()); - if ( !checkNullOrInvalidValue( pres ) || !checkNullOrInvalidValue( tmpc ) ) - return new Amount ( VolumetricDensity.UNIT ); - - pres = checkAndConvertInputAmountToExpectedUnits( pres, NcUnits.MILLIBAR ); - tmpc = checkAndConvertInputAmountToExpectedUnits( tmpc, SI.CELSIUS ); - - double tmpcVal = tmpc.doubleValue(); - if ( tmpcVal < -GempakConstants.TMCK ){ - System.out.println("From prDden: temperature must be greater than or equal to -273.15 "); - return new Amount ( VolumetricDensity.UNIT ); -// throw new InvalidRangeException("From prDden: temperature must be greater than or equal to -273.15 "); - } - /* Convert temperature and compute*/ - double tmpk = tmpc.getUnit().getConverterTo( SI.KELVIN ).convert( tmpcVal ); - prdden = 100 * pres.doubleValue() / ( GempakConstants.RDGAS * tmpk ); + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(selv)) + return new Amount(NonSI.INCH_OF_MERCURY); - return new Amount ( prdden, VolumetricDensity.UNIT ); - } - - /*** - * Computes the dewpoint depression, from tmpx and dwpx, both of which - * must be in the same units (one of Kelvin, Celsius or Farenheit) - * @param tmpx - Air temperature - * @param dwpx - Dewpoint temperature - * @return the dewpoint depresssion (in the same units are tmpx and dwpx) - * if both tmpx and dwpx are valid values - */ - - public static final Amount prDdep ( Amount tmpx, Amount dwpx) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDdep:"); - //System.out.println(" tmpx = " + tmpx.doubleValue()); - //System.out.println(" dwpx = " + dwpx.doubleValue()); -// checkNullOrInvalidValue(tmpx); -// checkNullOrInvalidValue(dwpx); - if ( !checkNullOrInvalidValue( tmpx ) || !checkNullOrInvalidValue( dwpx ) ) - return new Amount ( tmpx.getUnit() ); - - - Amount prDdep = null; - - dwpx = checkAndConvertInputAmountToExpectedUnits( dwpx, tmpx.getUnit() ); - double dewpointDepression = tmpx.doubleValue() - dwpx.doubleValue(); - prDdep = new Amount ( dewpointDepression , tmpx.getUnit()); - return prDdep; - } - - /** - * Computes DMAX, the maximum temperature obtained by - * comparing the 6-hour maximum at 00Z, - * the 6-hour maximum at 06Z and - * the local midnight maximum (reported at 00 LST) - * if either of the 6-hour values is missing, the maximum is set to missing. - * The inputs are in Celsius, the output in degrees Farenheit - * @param t00x - 6-hour maximum temperature at 00Z, Celsius - * @param t06x - 6-hour maximum temperature at 06Z, Celsius - * @param tdxc - Local midnight max temperature at 00 LST, Celsius - * @return The maximum of the 3 temperature values (in Farenheit) - * @throws NullPointerException - * - */ - public static final Amount prDmax ( Amount t00x, Amount t06x, Amount tdxc) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDmax:"); - //System.out.println(" t00x = " + t00x.doubleValue()); - //System.out.println(" t06x = " + t06x.doubleValue()); - //System.out.println(" tdxc= " + tdxc.doubleValue()); - -// checkNullOrInvalidValue(t00x); -// checkNullOrInvalidValue(t06x); - - if ( !checkNullOrInvalidValue( t00x ) || !checkNullOrInvalidValue( t06x ) ) - return new Amount ( NonSI.FAHRENHEIT ); - - t00x = checkAndConvertInputAmountToExpectedUnits( t00x, SI.CELSIUS ); - t06x = checkAndConvertInputAmountToExpectedUnits( t06x, SI.CELSIUS ); + selv = checkAndConvertInputAmountToExpectedUnits(selv, SI.METER); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + double seaLevelTempInKelvin = GempakConstants.TMCK + 15; + double hgtk = selv.getUnit().getConverterTo(SI.KILOMETER) + .convert(selv.doubleValue()); + double exponent = -(GempakConstants.GRAVTY + / (GempakConstants.GAMUSD * GempakConstants.RDGAS) * 1000); + // double base = pres * ( 1.0f - ( hgtk * GempakConstants.GAMUSD / + // seaLevelTempInKelvin ) ); + // float altm = (float) Math.pow( base, Math.exp( exponent ) ); + // return prAlti( pres * altm ); + double base = (1.0 - (hgtk * GempakConstants.GAMUSD / seaLevelTempInKelvin)); + double altm = Math.pow(base, exponent); + double altp = pres.doubleValue() * altm; + return (new Amount(altp, NonSI.INCH_OF_MERCURY)); + } - if ( tdxc != null && tdxc.doubleValue() > -9999 ) { - tdxc = checkAndConvertInputAmountToExpectedUnits( tdxc, SI.CELSIUS ); - double[] tempArray = { t00x.doubleValue(), t06x.doubleValue(), tdxc.doubleValue() }; - - Arrays.sort(tempArray); - double newTemp = SI.CELSIUS.getConverterTo(NonSI.FAHRENHEIT).convert(tempArray[2]); - return ( new Amount ( newTemp , NonSI.FAHRENHEIT ) ); - } - else{ - double t00xVal = t00x.doubleValue(); - double t06xVal = t06x.doubleValue(); - Amount dmax = ( t00xVal > t06xVal - ? checkAndConvertInputAmountToExpectedUnits(t00x, NonSI.FAHRENHEIT) - : checkAndConvertInputAmountToExpectedUnits(t06x, NonSI.FAHRENHEIT ) ); - return dmax; - } + /*** + * Computes the Ceiling converted to Mean Sea Level in hundreds of feet + * + * @param ceil + * - Ceiling in hundreds of feet + * @param selv + * - Station elevation in meters + * @return The ceiling converted to Mean Sea Level in hundreds of feet + * @throws NullPointerException + * + */ + public static final Amount prCmsl(Amount ceil, Amount selv) + throws InvalidValueException, NullPointerException { + // System.out.println("From prCmsl:"); + // System.out.println(" ceil = " + ceil.doubleValue()); + // System.out.println(" selv = " + selv.doubleValue()); + /* Sanity check */ + // checkNullOrInvalidValue( ceil ); + // checkNullOrInvalidValue( selv ); + if (!checkNullOrInvalidValue(ceil) || !checkNullOrInvalidValue(selv)) + return new Amount(NcUnits.HUNDREDS_OF_FEET); + + selv = checkAndConvertInputAmountToExpectedUnits(selv, + NcUnits.HUNDREDS_OF_FEET); + ceil = checkAndConvertInputAmountToExpectedUnits(ceil, + NcUnits.HUNDREDS_OF_FEET); + return (new Amount(selv.doubleValue() + ceil.doubleValue(), + NcUnits.HUNDREDS_OF_FEET)); + } + + /** + * Computes the wind direction in degrees from the input u and v components + * of velocity, both of which must be in the same units (either + * meters/second or knots) + * + * @param uX + * - U component of velocity + * @param vX + * - V component of velocity + * @return The wind direction in degrees + * @throws NullPointerException + * + */ + public static final Amount prDrct(Amount uX, Amount vX) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDrct:"); + // System.out.println(" uX = " + uX.doubleValue()); + // System.out.println(" vX = " + vX.doubleValue()); + + // checkNullOrInvalidValue(uX); + // checkNullOrInvalidValue(vX); + if (!checkNullOrInvalidValue(uX) || !checkNullOrInvalidValue(vX)) + return new Amount(NonSI.DEGREE_ANGLE); + + double vXVal = Double.NaN; + double uXVal = Double.NaN; + if (uX.getUnit() != vX.getUnit() + && uX.getUnit().isCompatible(vX.getUnit())) { + vXVal = vX.getUnit().getConverterTo(uX.getUnit()) + .convert(vX.doubleValue()); + vX = new Amount(vXVal, uX.getUnit()); + } + uXVal = uX.doubleValue(); + double prDrct = 0.0; + + if ((uXVal != 0) || (vXVal != 0)) { + prDrct = Math.atan2(-uXVal, -vXVal) * GempakConstants.RTD; + if (prDrct <= 0) + prDrct += 360; + } + return (new Amount(prDrct, NonSI.DEGREE_ANGLE)); + + } + + /** + * Computes the density of dry air given the pressure (in mb) and + * temperature (in Celsius) + * + * @param pres + * - Pressure in millibars + * @param tmpc + * - Temperature in Celsius + * @return Density of dry air in kg/(m**3) + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prDden(Amount pres, Amount tmpc) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDden:"); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + + double prdden = GempakConstants.RMISSD; + /* Check for bad data */ + // checkNullOrInvalidValue(pres); + // checkNullOrInvalidValue(tmpc); + + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(tmpc)) + return new Amount(VolumetricDensity.UNIT); + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + + double tmpcVal = tmpc.doubleValue(); + if (tmpcVal < -GempakConstants.TMCK) { + System.out + .println("From prDden: temperature must be greater than or equal to -273.15 "); + return new Amount(VolumetricDensity.UNIT); + // throw new + // InvalidRangeException("From prDden: temperature must be greater than or equal to -273.15 "); + } + /* Convert temperature and compute */ + double tmpk = tmpc.getUnit().getConverterTo(SI.KELVIN).convert(tmpcVal); + prdden = 100 * pres.doubleValue() / (GempakConstants.RDGAS * tmpk); + + return new Amount(prdden, VolumetricDensity.UNIT); + } + + /*** + * Computes the dewpoint depression, from tmpx and dwpx, both of which must + * be in the same units (one of Kelvin, Celsius or Farenheit) + * + * @param tmpx + * - Air temperature + * @param dwpx + * - Dewpoint temperature + * @return the dewpoint depresssion (in the same units are tmpx and dwpx) if + * both tmpx and dwpx are valid values + */ + + public static final Amount prDdep(Amount tmpx, Amount dwpx) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDdep:"); + // System.out.println(" tmpx = " + tmpx.doubleValue()); + // System.out.println(" dwpx = " + dwpx.doubleValue()); + // checkNullOrInvalidValue(tmpx); + // checkNullOrInvalidValue(dwpx); + if (!checkNullOrInvalidValue(tmpx) || !checkNullOrInvalidValue(dwpx)) + return new Amount(tmpx.getUnit()); + + Amount prDdep = null; + + dwpx = checkAndConvertInputAmountToExpectedUnits(dwpx, tmpx.getUnit()); + double dewpointDepression = tmpx.doubleValue() - dwpx.doubleValue(); + prDdep = new Amount(dewpointDepression, tmpx.getUnit()); + return prDdep; + } + + /** + * Computes DMAX, the maximum temperature obtained by comparing the 6-hour + * maximum at 00Z, the 6-hour maximum at 06Z and the local midnight maximum + * (reported at 00 LST) if either of the 6-hour values is missing, the + * maximum is set to missing. The inputs are in Celsius, the output in + * degrees Farenheit + * + * @param t00x + * - 6-hour maximum temperature at 00Z, Celsius + * @param t06x + * - 6-hour maximum temperature at 06Z, Celsius + * @param tdxc + * - Local midnight max temperature at 00 LST, Celsius + * @return The maximum of the 3 temperature values (in Farenheit) + * @throws NullPointerException + * + */ + public static final Amount prDmax(Amount t00x, Amount t06x, Amount tdxc) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDmax:"); + // System.out.println(" t00x = " + t00x.doubleValue()); + // System.out.println(" t06x = " + t06x.doubleValue()); + // System.out.println(" tdxc= " + tdxc.doubleValue()); + + // checkNullOrInvalidValue(t00x); + // checkNullOrInvalidValue(t06x); + + if (!checkNullOrInvalidValue(t00x) || !checkNullOrInvalidValue(t06x)) + return new Amount(NonSI.FAHRENHEIT); + + t00x = checkAndConvertInputAmountToExpectedUnits(t00x, SI.CELSIUS); + t06x = checkAndConvertInputAmountToExpectedUnits(t06x, SI.CELSIUS); + + if (tdxc != null && tdxc.doubleValue() > -9999) { + tdxc = checkAndConvertInputAmountToExpectedUnits(tdxc, SI.CELSIUS); + double[] tempArray = { t00x.doubleValue(), t06x.doubleValue(), + tdxc.doubleValue() }; + + Arrays.sort(tempArray); + double newTemp = SI.CELSIUS.getConverterTo(NonSI.FAHRENHEIT) + .convert(tempArray[2]); + return (new Amount(newTemp, NonSI.FAHRENHEIT)); + } else { + double t00xVal = t00x.doubleValue(); + double t06xVal = t06x.doubleValue(); + Amount dmax = (t00xVal > t06xVal ? checkAndConvertInputAmountToExpectedUnits( + t00x, NonSI.FAHRENHEIT) + : checkAndConvertInputAmountToExpectedUnits(t06x, + NonSI.FAHRENHEIT)); + return dmax; + } + + } - } - /** *
      * Computes the minimum temperature obtained by
      * comparing the 6-hour minimum at 12Z and the 6-hour minimum at 18Z.
      * if either of the 6-hour values is missing, the minimum is set to missing.
      * The inputs are in degrees C, the output in degrees F.
-     *  
- * @param t12n - 6-hour minimum temperature at 12Z, deg Celsius - * @param t18n - 6-hour minimum temperature at 18Z, deg Celsius - * @return the minimum temperature (in Farenheit) after comparing the two input values, if they exist - * @throws NullPointerException - * - */ - public static final Amount prDmin ( Amount t12n, Amount t18n ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDmin:"); - //System.out.println(" t12n = " + t12n.doubleValue()); - //System.out.println(" t18n = " + t18n.doubleValue()); -// checkNullOrInvalidValue( t12n ); -// checkNullOrInvalidValue( t18n ); - if ( !checkNullOrInvalidValue( t12n ) || !checkNullOrInvalidValue( t18n ) ) - return new Amount ( NonSI.FAHRENHEIT ); - - t12n = checkAndConvertInputAmountToExpectedUnits( t12n,SI.CELSIUS); - t18n = checkAndConvertInputAmountToExpectedUnits( t18n,SI.CELSIUS); - Amount minValue = ( t12n.doubleValue() < t18n.doubleValue() ? t12n : t18n); - return ( checkAndConvertInputAmountToExpectedUnits(minValue, NonSI.FAHRENHEIT) ); - - } - - /** - * Computes the dewpoint as the difference between the input temperature and dewpoint depression - * @param tmpx - temperature (in Celsius or Farenheit or Kelvin) - * @param dpdx - the dewpoint depression ( in the same units as the temperature) - * @return the dewpoint in the same units as ( in the same units as the temperature) - * @throws NullPointerException - * - */ - public static final Amount prDwdp( Amount tmpx, Amount dpdx ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDwdp:"); - //System.out.println(" tmpx = " + tmpx.doubleValue()); - //System.out.println(" dpdx = " + dpdx.doubleValue()); - -// checkNullOrInvalidValue(tmpx); -// checkNullOrInvalidValue(dpdx); - - if ( !checkNullOrInvalidValue( tmpx ) || !checkNullOrInvalidValue( dpdx ) ) - return new Amount ( tmpx.getUnit() ); - - Unit tempUnits = (Unit) tmpx.getUnit(); - Unit dewpointDepUnits = (Unit) dpdx.getUnit(); - if ( !tempUnits.equals( dewpointDepUnits) && tempUnits.isCompatible(dewpointDepUnits)){ - double dewpointDepValue= (double) dewpointDepUnits.getConverterTo(tempUnits).convert(dpdx.doubleValue()); - dpdx = new Amount ( dewpointDepValue, tempUnits); - } - - Amount dewpointTemperature = new Amount ( tmpx.doubleValue() - dpdx.doubleValue(), tempUnits ); - return dewpointTemperature; - } - - /** - * Computes the dewpoint ( in Celsius ) from the mixing ratio (in grams/kilogram) - * and the pressure (in mb) - * @param rmix - the mixing ratio (in grams/kilogram) - * @param pres - the pressure (in mb) - * @return the dewpoint (in Celsius), if both the input values are valid - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prDwpt( Amount rmix, Amount pres) throws InvalidValueException, NullPointerException{ - //System.out.println("From prDwpt:"); - //System.out.println(" rmix = " + rmix.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - Amount prDwpt = null; -// checkNullOrInvalidValue( rmix ); -// checkNullOrInvalidValue( pres ); - if ( !checkNullOrInvalidValue( rmix ) || !checkNullOrInvalidValue( pres ) ) - return new Amount ( SI.CELSIUS ); - - rmix = checkAndConvertInputAmountToExpectedUnits( rmix, Unit.ONE ); - pres = checkAndConvertInputAmountToExpectedUnits( pres, NcUnits.MILLIBAR ); - double mixingRatioValue = rmix.doubleValue() ; - double pressureValue = pres.doubleValue(); - if ( mixingRatioValue <= 0 ){ - System.out.println("From prDwpt() - mixing ratio must be greater than 0"); - return new Amount ( SI.CELSIUS ); -// throw new InvalidRangeException("From prDwpt() - mixing ratio must be greater than 0"); - } - if ( pressureValue <= 0 ){ - System.out.println("From prDwpt() - pres must be greater than 0"); - return new Amount ( SI.CELSIUS ); -// throw new InvalidRangeException("From prDwpt() - pres must be greater than 0"); - } - /*Convert gram/kilogram to gram/gram*/ - double ratio = mixingRatioValue / 1000; - - /*Calculate vapor pressure from mixing ratio and pressure*/ - double vaporPressure = ( pressureValue * ratio ) / ( 0.62197 + ratio ); - - /*Correct vapor pressure*/ - vaporPressure = vaporPressure / ( 1.001 + ( ( pressureValue - 100.0 ) / 900) * .0034 ) ; - - /*Calculate dewpoint*/ - double dewPointValue = ( double ) ( Math.log ( vaporPressure / 6.112 ) * 243.5 / ( 17.67 - Math.log( vaporPressure / 6.112 ) ) ) ; - prDwpt = new Amount ( dewPointValue, SI.CELSIUS); - return prDwpt; - } - - /*** - * Computes the Fosberg index from the temperature, relative humidity and wind speed - * at the surface. - * @param tmpc - Temperature in Celsius - * @param relh - Relative humidity in percent - * @param sped - Wind speed in meters/second - * @return the Fosberg index - * @throws NullPointerException - * - */ - public static final Amount prFosb( Amount tmpc, Amount relh, Amount sped) throws InvalidValueException, NullPointerException{ - //System.out.println("From prFosb:"); - //System.out.println("Temperature ( in K ) is: " + tmpc.doubleValue()); - //System.out.println("Relative Humidity is: " + relh.doubleValue()); - //System.out.println("Wind Speed is: " + sped.doubleValue()); -// checkNullOrInvalidValue( tmpc ); -// checkNullOrInvalidValue( relh ); -// checkNullOrInvalidValue( sped ); - if ( !checkNullOrInvalidValue( tmpc ) || !checkNullOrInvalidValue( relh ) - || !checkNullOrInvalidValue( sped )) - return new Amount ( Unit.ONE ); - - tmpc = checkAndConvertInputAmountToExpectedUnits( tmpc, SI.CELSIUS ); - relh = checkAndConvertInputAmountToExpectedUnits( relh, NonSI.PERCENT ); - sped = checkAndConvertInputAmountToExpectedUnits( sped, SI.METERS_PER_SECOND ); - - /*Change temperature to degrees Fahrenheit*/ - double tf = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT).convert(tmpc.doubleValue()); - - /*Convert wind speed from meters/second to knots*/ - double smph = sped.getUnit().getConverterTo( NonSI.MILES_PER_HOUR ).convert( sped.doubleValue() ); - - - double A = 0.03229; - double B = 0.281073; - double C = 0.000578; - double D = 2.22749; - double E = 0.160107; - double F = 0.014784; - double G = 21.0606; - double H = 0.005565; - double P = 0.00035; - double Q = 0.483199; - double R = 0.3002; -// float T = 9.0f/5.0f; -// float U = 1.9425f; -// float V = 0.868976f; - double fw = GempakConstants.RMISSD; - double relhVal = relh.doubleValue(); - if ( relhVal <= 10 ) { - fw = A + B * relhVal - C * relhVal * tf; - } - else if ( relhVal <= 50 ) { - fw = D + E * relhVal - F * tf; - } - else{ - fw = G + H *relhVal*relhVal - P * relhVal * tf - Q * relhVal; - } - - double sss = ( double ) ( Math.sqrt ( 1. + ( smph * smph ) )); - double fwd = fw / 30; - double fwd2 = fwd * fwd; - double fwd3 = fwd2 * fwd; - double fire = 1 - 2 * fwd + 1.5f * fwd2 - 0.5f * fwd3; - - /*Find the Fosberg Index*/ - - double prfosb = ( fire * sss ) / R; - - return ( new Amount ( prfosb , Unit.ONE ) ); - } - - /** - *
-	 * Computes the Southern Region/CPC Rothfusz heat index.
-	 * 
-	 * The Rothfusz regression is optimal for TMPF > ~80 and RELH > ~40%.   
-	 * This code applies a simple heat index formula and then resorts to   
-	 * the Rothfusz regression only if the simple heat index exceeds 80,   
-	 * implying temperatures near, but slightly below 80.  To make the    
-	 * simple calculation continuous with the values obtained from the   
-	 * Rothfusz regression, the simple result is averaged with TMPF in    
-	 * computing the simple heat index value.                               
-	 * Source:  NWS Southern Region SSD Technical Attachment SR 90-23  7/1/90.  
-	 * Heat Index was originally known as the apparent temperature index 
-	 * (Steadman, JAM, July, 1979).                                                                  
-	 * This code includes adjustments made by the CPC for low RELH at high  
-	 * TMPF and high RELH for TMPF in the mid 80's.
-	 * 
-	 * 
-	 * @param tmpf  - the input air temperature
-	 * @param relh   - the relative humidity 
-	 * @return the heat index (in deg Farenheit) if both 
-	 * the input air temperature and relative humidity
-	 * are valid values 
-	 * 
- * @throws NullPointerException - * - */ - public static final Amount prHeat( Amount tmpf, Amount relh ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prHeat:"); - //System.out.println(" tmpf = " + tmpf.doubleValue()); - //System.out.println(" relh = " + relh.doubleValue()); - - double prheat = GempakConstants.RMISSD; -// checkNullOrInvalidValue( tmpf ); -// checkNullOrInvalidValue( relh ); - if ( !checkNullOrInvalidValue( tmpf ) || !checkNullOrInvalidValue( relh ) ) - return new Amount ( Unit.ONE ); - - tmpf = checkAndConvertInputAmountToExpectedUnits( tmpf, NonSI.FAHRENHEIT ); - relh = checkAndConvertInputAmountToExpectedUnits( relh, NonSI.PERCENT ); - double tmpfVal = tmpf.doubleValue(); - double relhVal = relh.doubleValue(); - /*If the temperature is less than 40 degrees, set the heat index to the temperature*/ - if ( tmpfVal <= 40 ) - prheat = tmpfVal; - else{ - /* - * Compute a simple heat index. If the value is less than 80 deg F - * use it - */ - prheat = ( float ) ( 61 + ( tmpfVal - 68 ) * 1.2 + relhVal * 0.094); - prheat = ( float ) ( ( tmpfVal + prheat ) * 0.5); - /*Else compute the full regression value*/ - if ( prheat >= 80.0 ){ - double t2 = tmpfVal * tmpfVal; - double r2 = relhVal * relhVal; - prheat = ( float ) ( -42.379 - + 2.04901523 * tmpfVal - + 10.14333127 * relhVal - - 0.22475541 * tmpfVal * relhVal - - 0.00683783 * t2 - - 0.05481717 * r2 - + 0.00122874 * t2 * relhVal - + 0.00085282 * tmpfVal *r2 - - 0.00000199 * t2 * r2 ); - /* - * Adjust for high regression at low relative humidity for temperatures above 80 degrees F. - */ - if ( ( relhVal <= 13.0 ) && ( ( tmpfVal >= 80.0 ) &&( tmpfVal <= 112.0 ) ) ) { - float adj1 = ( float) ( ( 13. - relhVal ) / 4 ); - float adj2 = ( float ) ( Math.sqrt ( ( 17 - Math.abs (tmpfVal - 95) ) / 17 ) ); - float adj = adj1 * adj2; - prheat -= adj; - } - /* - * Adjust for low regression at high relative humidity and temperatures in the mid-80s - */ - else if ( ( relhVal > 85 ) && ( ( tmpfVal >= 80.0 ) &&( tmpfVal <= 87.0 ) ) ){ - float adj1 = ( float) ( ( relhVal - 85.0 ) / 10.0 ); - float adj2 = ( float ) ( ( 87.0 - tmpfVal ) / 5); - float adj = adj1 * adj2; - prheat += adj; - } - } - } - return ( new Amount ( prheat,NonSI.FAHRENHEIT ) ); - } - - /** - * Computes the humiture index from the air temperature and the dew point - * temperature using the equation: PR_HMTR = TMPF + ( PR_VAPR ( DWPC ) - 21 ) - * @param tmpf - the air temperature (in Farenheit) - * @param dwpf - the dew point (in Farenheit) - * @return the humiture index if both the air temperature and the dewpoint temperature are - * valid values - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prHmtr( Amount tmpf, Amount dwpf) throws InvalidValueException, NullPointerException{ - //System.out.println("From prHmtr:"); - //System.out.println(" tmpf = " + tmpf.doubleValue()); - //System.out.println(" dwpf = " + dwpf.doubleValue()); - - double prhmtr = GempakConstants.RMISSD; - if ( !checkNullOrInvalidValue( tmpf ) || !checkNullOrInvalidValue( dwpf )) - return new Amount ( Unit.ONE ); - - tmpf = checkAndConvertInputAmountToExpectedUnits( tmpf, NonSI.FAHRENHEIT ); - dwpf = checkAndConvertInputAmountToExpectedUnits( dwpf, NonSI.FAHRENHEIT ); - - Amount dwpc = checkAndConvertInputAmountToExpectedUnits(dwpf, SI.CELSIUS); - Amount vapr = prVapr( dwpc ); - if ( !checkNullOrInvalidValue( vapr ) ) - return new Amount ( Unit.ONE); - - prhmtr = tmpf.doubleValue() + (vapr.doubleValue() - 21 ); - - return ( new Amount ( prhmtr, Unit.ONE)); - } - - /** - * Computes the rate of ice accretion/growth of ice - * on a vessel in salt water, in units of inches per 3 hours (the WMO standard) - * The formula used is - * IGRO = ( A*pr + B*pr*pr + *pr*pr*pr ) * CVFAC - * where - * A = 2.73 * 10e-2 - * B = 2.91 * 10e-4 - * C = 1.84 * 10e-6 - * pr = ( sped * ( -1.7 - tmpc ) ) / ( 1 + 0.4 * ( sstc + 1.7 ) ) - * (priesendorfer regression) - * and - * CVFAC = 1.1811, to convert cm/hr to in/3hr. - * - * @param tmpc - the observed surface air temperature in Celsius - * @param sstc - the observed surface sea temperature in Celsius - * @param sped - the observed wind speed - * @return the rate of ice growth if all the input values are valid and lie between specific limits - * and if the rate of ice growth that is computed is greater than or equal to 0, - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prIgro ( Amount tmpc, Amount sstc, Amount sped) throws InvalidValueException, NullPointerException{ - //System.out.println("From prIgro:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" sstc = " + sstc.doubleValue()); - //System.out.println(" sped = " + sped.doubleValue()); - - double prigro = GempakConstants.RMISSD; -// checkNullOrInvalidValue( tmpc ) ; -// checkNullOrInvalidValue( sstc ) ; -// checkNullOrInvalidValue( sped ) ; - - if ( !checkNullOrInvalidValue( tmpc ) || !checkNullOrInvalidValue( sstc ) - || !checkNullOrInvalidValue( sped )) - return new Amount ( NcUnits.INCHES_PER_THREE_HOURS ); - - checkAndConvertInputAmountToExpectedUnits( tmpc, SI.CELSIUS ); - checkAndConvertInputAmountToExpectedUnits( sstc, SI.CELSIUS ); - - //TODO: verify that wind speed can have any unit - double tmpcVal = tmpc.doubleValue(); - double sstcVal = sstc.doubleValue(); - double spedVal = sped.doubleValue(); - - /* Check that these values are within the valid range */ - - if ( spedVal < 0 || spedVal > 50){ -// throw new InvalidRangeException("The wind speed must lie between 0 and 50. Both limits inclusive"); - System.out.println("The wind speed must lie between 0 and 50. Both limits inclusive"); - return new Amount ( NcUnits.INCHES_PER_THREE_HOURS ); - } - if ( tmpcVal < -20 || tmpcVal > 0 ){ -// throw new InvalidRangeException("The observed surface air temperature must lie between -20 and 0. Both limits inclusive"); - System.out.println("The observed surface air temperature must lie between -20 and 0. Both limits inclusive"); - return new Amount ( NcUnits.INCHES_PER_THREE_HOURS ); - } - if ( sstcVal < -1.7f || sstcVal > 12 ){ -// throw new InvalidRangeException("The observed surface sea temperature must lie between -1.7 and 12. Both limits inclusive"); - } - double A = 0.0273f; - double B = 0.000291f; - double C = 0.00000184f; - double cvfac = 1.1811f; // to convert cm/hr to in per 3 hours - double pr = ( ( spedVal * ( -1.7 - tmpcVal ) ) / ( 1 + 0.4 * ( sstcVal + 1.7 ) ) ); // Compute the Priesendorfer regression - double pr2 = pr * pr; - prigro = ( A * pr + B * pr2 + C * pr * pr2 ) * cvfac; - if ( prigro < 0 ){ -// throw new InvalidRangeException("The rate of ice growth must be greater than or equal to 0"); - System.out.println("The rate of ice growth must be greater than or equal to 0"); - return new Amount ( NcUnits.INCHES_PER_THREE_HOURS ); - } - return ( new Amount ( prigro, NcUnits.INCHES_PER_THREE_HOURS ) ); - } - - /** - * Computes the latent heat of vaporization at constant pressure - * from the input temperature (in Celsius) using the equation: - * LHVP = ( 2.500 - .00237 * TMPC ) * 10E6 - * LHVP is in J/kg. - * @param tmpc - the input temperature (in Celsius) - * @return the latent heat of vaporization at constant pressure if - * the input temperature is valid - * @throws NullPointerException - * - */ - public static final Amount prLhvp( Amount tmpc) throws InvalidValueException, NullPointerException{ - - //System.out.println("From prLhvp:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - -// checkNullOrInvalidValue(tmpc); - if ( !checkNullOrInvalidValue( tmpc ) ) - return new Amount ( NcUnits.JOULES_PER_KILOGRAM ); - - tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); - double latentHeatOfVapr = ( float) ( (2.500 - 0.00237 * tmpc.doubleValue()) * 1000000); - return ( new Amount ( latentHeatOfVapr, NcUnits.JOULES_PER_KILOGRAM ) ); - } - - /** - * Computes the temperature of a parcel lifted (or sunk) - * adiabatically to a given pressure. - * @param thta - Potential temperature in Kelvin - * @param thte - Equivalent potential temp in Kelvin - * @param pres - Lifted pressure in millibar - * @return the lifted temperature in Celsius, if all the input parameters are valid - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prLtmp ( Amount thta, Amount thte, Amount pres ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prLtmp:"); - //System.out.println(" thta = " + thta.doubleValue()); - //System.out.println(" thte = " + thte.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - - double prltmp = GempakConstants.RMISSD; -// checkNullOrInvalidValue(thta); -// checkNullOrInvalidValue(thte); -// checkNullOrInvalidValue(pres); - - if ( !checkNullOrInvalidValue( thta ) || !checkNullOrInvalidValue( thte ) - || !checkNullOrInvalidValue( pres )) - return new Amount ( SI.CELSIUS ); - - thta = checkAndConvertInputAmountToExpectedUnits(thta, SI.KELVIN); - thte = checkAndConvertInputAmountToExpectedUnits(thte, SI.KELVIN); - - if ( pres.doubleValue() <= 0 ) - pres = new Amount ( 500, NcUnits.MILLIBAR ); - - /*Compute parcel temperatures on moist and dry adiabats*/ - Amount tmpe = prTmst ( thte, pres, new Amount (0, SI.KELVIN )); - Amount tmpd = prTmpk ( pres, thta); - checkNullOrInvalidValue(tmpe); - checkNullOrInvalidValue(tmpd); - /* - * ( Non-Javadoc ) - * The correct parcel temperature is the warmer of the - * temperature on the dry adiabat and the temperature on the - * moist adiabat. - */ - - double tmpeVal = tmpe.doubleValue(); - double tmpdVal = tmpd.doubleValue(); - if ( tmpeVal > tmpdVal ){ - prltmp = SI.KELVIN.getConverterTo(SI.CELSIUS).convert( tmpeVal ); - }else{ - prltmp = SI.KELVIN.getConverterTo(SI.CELSIUS).convert( tmpdVal ); - } - - return ( new Amount ( prltmp, SI.CELSIUS )); - } - - /** - * Computes the mountain obscuration threshold met indicator - * @param cmsl - Ceiling converted to MSL in 100's of ft - * @param otval - Mountain obscuration threshold in 100's of ft - * @return The mountain obscuration threshold met indicator if - * the input values are valid - * @throws NullPointerException - * - */ - public static final Amount prMobs ( Amount cmsl, Amount otval) throws InvalidValueException, NullPointerException{ - //System.out.println("From prMobs:"); - //System.out.println(" cmsl = " + cmsl.doubleValue()); - //System.out.println(" otval = " + otval.doubleValue()); - -// checkNullOrInvalidValue( cmsl ); -// checkNullOrInvalidValue( otval ); - - if ( !checkNullOrInvalidValue( cmsl ) || !checkNullOrInvalidValue( otval ) ) - return new Amount ( Unit.ONE ); - - cmsl = checkAndConvertInputAmountToExpectedUnits( cmsl, NcUnits.HUNDREDS_OF_FEET ); - otval = checkAndConvertInputAmountToExpectedUnits( otval, NcUnits.HUNDREDS_OF_FEET ); - return ( cmsl.doubleValue() < otval.doubleValue() ? new Amount ( 1, Unit.ONE) : new Amount ( 1, Unit.ONE) ); - } - - /** - * Computes the mixing ratio in grams/kilograms from the dewpoint ( in Celsius ) - * and the pressure ( in mb) using the equation: - * MIXR = .62197 * ( e / ( PRES - e ) ) * 1000. - * where - * e = VAPR * corr - * corr = (1.001 + ( ( PRES - 100. ) / 900. ) * .0034) - * ( University of Wisconsin green sheet ). - * This method can also be used for the folloiwng computations: - * MIXS from TMPC and PRES - * SMXR from DWPC and PALT - * SMXS from TMPC and PALT - * - * @param dwpc - the dewpoint ( in Celsius ) - * @param pres - the pressure ( in mb) - * @return the missing ratio ( in grams / kilograms ) if both the input - * parameters are valid - * @throws NullPointerException - * - */ - public static final Amount prMixr ( Amount dwpc, Amount pres ) { - //System.out.println("From prMixr:"); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - - Amount prmixr = new Amount ( -9999.0, Unit.ONE); -// checkNullOrInvalidValue(pres); -// checkNullOrInvalidValue(dwpc); - - if ( !checkNullOrInvalidValue( pres ) || !checkNullOrInvalidValue( dwpc ) ) - return new Amount ( NcUnits.GRAMS_PER_KILOGRAM ); - - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); - - /*Calculate vapor pressure*/ - Amount vapr = prVapr ( dwpc ); - - if ( !checkNullOrInvalidValue(vapr) ) - return new Amount ( NcUnits.GRAMS_PER_KILOGRAM ); - - vapr = checkAndConvertInputAmountToExpectedUnits(vapr, NcUnits.MILLIBAR); - double pressureValue = pres.doubleValue(); - double vaporPressureValue = vapr.doubleValue(); - /* - * (Non-Javadoc) - * corr is a correction to the vapor pressure since the atmosphere is not an ideal gas. - */ - double corr = ( double ) (1.001 + ( ( pressureValue - 100 ) / 900 ) * 0.0034 ); - double e = corr * vaporPressureValue; - - /* - * Test for unphysical case of large E at low PRES - */ - if ( e <= ( 0.5 * pressureValue ) ){ - /*Calculate mixing ratio */ - prmixr = new Amount ( ( double ) ( 0.62197 * ( e / ( pressureValue - e ) ) * 1000 ) , NcUnits.GRAMS_PER_KILOGRAM ); - } - return prmixr; - } - - /** - * Extracts the pressure change ( in millibars ) from - * the pressure tendency information - * @param p03d - Pressure tendency information - * @return the pressure change ( in mb ) - * @throws NullPointerException - * - */ - //TODO : remove it to make it a part of display options or let it stay? - public static final Amount prP03c( Amount p03d) throws InvalidValueException, NullPointerException{ - //System.out.println("From prP03c:"); - //System.out.println(" p03d = " + p03d.doubleValue()); - - - double prp03c = GempakConstants.RMISSD; -// checkNullOrInvalidValue( p03d ); - if ( !checkNullOrInvalidValue( p03d ) ) - return new Amount ( NcUnits.MILLIBAR ); - - double p03dVal = p03d.doubleValue(); - float[] psign = {1, 1, 1, 1, 0, -1, -1, -1, -1}; - int itendc = ( int ) ( p03dVal / 1000); - float ptend = ( float ) ( ( ( int ) p03dVal ) % 1000 ) / 10f; - // TODO: compare tests with legacy - if ( itendc < psign.length ) - prp03c = psign[itendc] * ptend; - - return ( new Amount ( prp03c , NcUnits.MILLIBAR ) ); - } - - - /** - * Computes station pressure from altimeter and station elevation using - * the equation - * PALT = ALTM * ( 1 - ( SELK * GAMUSD / To ) ) ** expo - * where - * SELK = SELV / 1000 - * To = US Std. Atmos. sea level temp in Kelvin - * = TMCK + 15 - * expo = GRAVTY / ( GAMUSD * RDGAS ) * 1000 - * Wallace and Hobbs. - * - * @param altm - Altimeter in millibars - * @param selv - Station elevation in meters - * @return the pressure in millibars if none of the input values are missing - * @throws NullPointerException - * - */ - public static final Amount prPalt ( Amount altm, Amount selv) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPalt:"); - //System.out.println(" altm = " + altm.doubleValue()); - //System.out.println(" selv = " + selv.doubleValue()); - - // checkNullOrInvalidValue( altm ); -// checkNullOrInvalidValue( selv ); - - if ( !checkNullOrInvalidValue( altm ) || !checkNullOrInvalidValue( selv ) ) - return new Amount ( NcUnits.MILLIBAR ); - - altm = checkAndConvertInputAmountToExpectedUnits( altm, NcUnits.MILLIBAR ); - selv = checkAndConvertInputAmountToExpectedUnits( selv, SI.METER ); - double hgtk = selv.getUnit().getConverterTo( SI.KILOMETER ).convert( selv.doubleValue() ); - - /*Calculate the exponent*/ - double expo = ( GempakConstants.GRAVTY / ( GempakConstants.GAMUSD * GempakConstants.RDGAS ) * 1000.0f); - - /*Calculate pressure*/ - double prpalt = ( altm.doubleValue() * Math.pow( ( 1 - ( hgtk * GempakConstants.GAMUSD/ ( GempakConstants.TMCK + 15 ) ) ) , expo ) ); - - return ( new Amount ( prpalt, NcUnits.MILLIBAR ) ); - } - - /** - * Computes the lifted condensation level pressure ( in mb ) for a parcel of air - * from TMPC, PRES, and TLCL. TLCL may be computed using PR_TLCL. The equation - * used is a modified Poisson equation: - * PLCL = PRES * ( TLCL / TMPK ) ** ( 1 / RKAPPA ) - * @param tmpc - Temperature ( in Celsius ) before lifting the air parcel - * @param pres - Pressure ( in mb ) before lifting the air parcel - * @param tlcl - Temperature ( in Kelvin ) at the lifted condensation level - * @return the pressure at the lifted condensation level, if all the inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prPlcl ( Amount tmpc, Amount pres, Amount tlcl) throws InvalidValueException, NullPointerException{ - double prplcl = GempakConstants.RMISSD; - //System.out.println("From prPlcl:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" tlcl = " + tlcl.doubleValue()); - -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(pres); -// checkNullOrInvalidValue(tlcl); - if ( !checkNullOrInvalidValue( tmpc ) || !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( tlcl )) - return new Amount ( NcUnits.MILLIBAR ); - - tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - tlcl = checkAndConvertInputAmountToExpectedUnits(tlcl, SI.KELVIN); - Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN) ; - double tclValue = tlcl.doubleValue(); - double tmpkValue = tmpk.doubleValue(); - double presValue = pres.doubleValue(); - prplcl = ( double ) ( presValue * Math.pow( ( tclValue / tmpkValue ) , ( 1 / GempakConstants.RKAPPA ) ) ); - return new Amount ( prplcl, NcUnits.MILLIBAR ); - } - - /** - *
-	 *  Computes the mean sea level pressure ( in mb ) from the station pressure ( in mb ),
-	 *  the temperature ( in deg Celsius), the dewpoint ( in deg Celsius ) and 
-	 *  the station elevation ( in meters ) using the equation:
-	 *  	PMSL = PRES * EXP ( ( GRAVTY * SELV ) / ( RDGAS * TVAVE ) ) 
-	 *     where
-	 *     		 TVAVE = avg virtual temp between station and sea level 
-	 *     		        = TVRK + ( DELTV / 2 )
-	 *     		 DELTV = GAMUSD * SELV / 1000
-	 *  Wallace and Hobbs.
-	 * @param pres - the station pressure ( in mb )
-	 * @param tmpc - the temperature ( in deg Celsius)
-	 * @param dwpc - the dewpoint ( in deg Celsius )
-	 * @param selv - the station elevation ( in meters )
-	 * @return the mean sea level pressure ( in mb ) if all the inputs are valid
      * 
- * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prPmsl ( Amount pres, Amount tmpc, Amount dwpc, Amount selv ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPmsl:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - //System.out.println(" selv = " + selv.doubleValue()); -// checkNullOrInvalidValue( pres ); -// checkNullOrInvalidValue( tmpc ); -// checkNullOrInvalidValue( dwpc ); -// checkNullOrInvalidValue( selv ); - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( dwpc ) - || !checkNullOrInvalidValue( selv ) ) - return new Amount ( NcUnits.MILLIBAR ); - - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - tmpc = checkAndConvertInputAmountToExpectedUnits(pres, SI.CELSIUS); - dwpc = checkAndConvertInputAmountToExpectedUnits(pres, SI.CELSIUS); - selv = checkAndConvertInputAmountToExpectedUnits(pres, SI.METER); - - /*Calculate virtual temperature*/ - Amount tv = prTvrk ( tmpc, dwpc, pres); - - /*deltaV and tVave*/ - double selvVal = selv.doubleValue(); - double deltaV = selvVal * GempakConstants.GAMUSD / 1000; - double tVave = tv.doubleValue() + ( deltaV / 2); - double mathFormula = ( GempakConstants.GRAVTY * selvVal ) / ( GempakConstants.RDGAS * tVave ); - double prpmsl = ( pres.doubleValue() * Math.exp(mathFormula)); - - return ( new Amount ( prpmsl, NcUnits.MILLIBAR ) ); - } - - /** - * Computes the maximum precipitation amount for upto 4 preciptiation values in inches - * @param p01 - First precipitation amount - * @param p02 - Second precipitation amount - * @param p03 - Third precipitation amount - * @param p04 - Fourth precipitation amount - * @return the maximum precipitation - * @throws NullPointerException - * - */ - - public static final Amount prPr6x ( Amount p01, Amount p02, Amount p03, Amount p04 ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPr6x:"); - //System.out.println(" p01 = " + p01.doubleValue()); - //System.out.println(" p02 = " + p02.doubleValue()); - //System.out.println(" p03 = " + p03.doubleValue()); - //System.out.println(" p04 = " + p04.doubleValue()); - Amount[] tempArray = {p01, p02, p03, p04 }; - int index = 0; - double[] tempDblArray = new double[ 4 ]; - for ( Amount thisAmount : tempArray){ - if (!checkNullOrInvalidValue(thisAmount)){ - return new Amount ( NonSI.INCH ); - } - - if ( thisAmount.getUnit() != NonSI.INCH ) { - thisAmount = checkAndConvertInputAmountToExpectedUnits(thisAmount, NonSI.INCH); - tempArray[index] = thisAmount; - } - tempDblArray[ index ] = thisAmount.doubleValue(); - index++; - } - - Arrays.sort(tempDblArray); - return ( new Amount ( tempDblArray[ 3 ], NonSI.INCH ) ); - } - - /** - * Computes PR24, the 24-hour precipitation calculated by - * summing four 6-hour precipitation values - * @param p01 - First 6-hour precipitation amount - * @param p02 - Second 6-hour precipitation amount - * @param p03 - Third 6-hour precipitation amount - * @param p04 - Fourth 6-hour precipitation amount - * @return the total 24-hour precipitation amount - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prPr24 ( Amount p01, Amount p02, Amount p03, Amount p04 ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPr24:"); - //System.out.println(" p01 = " + p01.doubleValue()); - //System.out.println(" p02 = " + p02.doubleValue()); - //System.out.println(" p03 = " + p03.doubleValue()); - //System.out.println(" p04 = " + p04.doubleValue()); - -// checkNullOrInvalidValue( p01 ); -// checkNullOrInvalidValue( p02 ); -// checkNullOrInvalidValue( p03 ); -// checkNullOrInvalidValue( p04 ); - - if ( !checkNullOrInvalidValue( p01 ) - || !checkNullOrInvalidValue( p02 ) - || !checkNullOrInvalidValue( p03 ) - || !checkNullOrInvalidValue( p04 ) ) - return new Amount ( NonSI.INCH ); - - Amount[] tempArray = {p01, p02, p03, p04 }; - Arrays.sort(tempArray); - - Amount p24 = tempArray[3]; - double p01Val = p01.doubleValue(); - double p02Val = p02.doubleValue(); - double p03Val = p03.doubleValue(); - double p04Val = p04.doubleValue(); - double p24Val = p24.doubleValue(); - - if ( p24Val > 0 ){ - p24Val = 0; - if ( p01Val > 0 ) - p24Val += p01Val; - - if ( p02Val > 0 ) - p24Val += p01Val; - - if ( p03Val > 0 ) - p24Val += p01Val; - - if ( p04Val > 0 ) - p24Val += p01Val; - - } - - if ( p24Val < 0 ){ -// throw new InvalidRangeException("From prPr24: the total 24 hour precipitation amount cannot be less than 0 inches"); - System.out.println("From prPr24: the total 24 hour precipitation amount cannot be less than 0 inches"); - return new Amount ( NonSI.INCH ); - } - return ( new Amount ( p24Val, NonSI.INCH ) ); - } - - /** - * Computes the station pressure ( in mb ) from the temperature ( in deg Celsius ) - * and the potential temperature ( in Kelvin ) using Poisson's equation: - * PRES = 1000. * ( PR_TMCK (TMPC) / THTA ) ** (1 / RKAPPA) - * @param tmpc - temperature (in deg Celsius) - * @param thta - potential temperature ( in Kelvin ) - * @return the station pressure ( in mb ) if both the inputs are valid - * @throws NullPointerException * - * @throws InvalidRangeException + * @param t12n + * - 6-hour minimum temperature at 12Z, deg Celsius + * @param t18n + * - 6-hour minimum temperature at 18Z, deg Celsius + * @return the minimum temperature (in Farenheit) after comparing the two + * input values, if they exist + * @throws NullPointerException + * */ - public static final Amount prPres ( Amount tmpc, Amount thta ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPres:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" thta = " + thta.doubleValue()); + public static final Amount prDmin(Amount t12n, Amount t18n) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDmin:"); + // System.out.println(" t12n = " + t12n.doubleValue()); + // System.out.println(" t18n = " + t18n.doubleValue()); + // checkNullOrInvalidValue( t12n ); + // checkNullOrInvalidValue( t18n ); + if (!checkNullOrInvalidValue(t12n) || !checkNullOrInvalidValue(t18n)) + return new Amount(NonSI.FAHRENHEIT); - -// checkNullOrInvalidValue( tmpc ); -// checkNullOrInvalidValue( thta ); - - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( thta ) ) - return new Amount ( NcUnits.MILLIBAR ); - - tmpc = checkAndConvertInputAmountToExpectedUnits( tmpc, SI.CELSIUS ); - thta = checkAndConvertInputAmountToExpectedUnits( thta, SI.KELVIN ); - double tmpcVal = tmpc.doubleValue(); - double thtaVal = thta.doubleValue(); - if ( tmpcVal <= -GempakConstants.TMCK ){ - // throw new InvalidRangeException("From prPres: the temperature must be greater than -273.15"); - System.out.println("From prPres: the temperature must be greater than -273.15"); - return new Amount ( NcUnits.MILLIBAR ); - } - if ( thtaVal <= 0 ){ -// throw new InvalidRangeException("From prPres: the potential temperature must be greater than 0"); - System.out.println("From prPres: the potential temperature must be greater than 0"); - return new Amount ( NcUnits.MILLIBAR ); - } - double tmpkVal = tmpc.getUnit().getConverterTo( SI.KELVIN ).convert( tmpcVal ); - double prpres = ( float ) ( 1000 * Math.pow(tmpkVal / thtaVal, 1 / GempakConstants.RKAPPA ) ); + t12n = checkAndConvertInputAmountToExpectedUnits(t12n, SI.CELSIUS); + t18n = checkAndConvertInputAmountToExpectedUnits(t18n, SI.CELSIUS); + Amount minValue = (t12n.doubleValue() < t18n.doubleValue() ? t12n + : t18n); + return (checkAndConvertInputAmountToExpectedUnits(minValue, + NonSI.FAHRENHEIT)); - return ( new Amount ( prpres , NcUnits.MILLIBAR ) ); - } - - - /** - * Extracts the symbol code from the pressure tendency information. - * The code number is returned follow by 999 so that the output is a 4-digit number. - * @param p03d - the pressure tendency information - * @return the pressure tendency symbol code if the input is valid - * @throws NullPointerException - * - */ - //TODO add it to the Met Parameters or remove it and make it part of the display options instead? - public static final Amount prPtsy ( Amount p03d ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prPtsy:"); - //System.out.println(" p03d = " + p03d.doubleValue()); - - if ( !checkNullOrInvalidValue( p03d )) - return new Amount ( Unit.ONE); - - double p03dVal = p03d.doubleValue(); - int prptsy = -9999; - if ( !( p03dVal < 0 ) & !( p03dVal >= 9000 ) ) { - prptsy = ( ( int ) ( p03dVal / 1000 ) ) * 1000 + 999 ; - } - return ( new Amount ( prptsy, Unit.ONE ) ); - } - -/** - * Computes the relative humidity ( in percent ) from the input temperature and dewpoint using - * the equation: - * RELH = VAPR / VAPS * 100 - * where - * VAPR = vapor pressure - * = PR_VAPR ( DWPC ) - * VAPS = saturation vapor pressure - * = PR_VAPR ( TMPC ) - * - * @param tmpc - temperature ( in Celsius ) - * @param dwpc - dewpoint ( in Celsius) - * @return the relative humidity ( in percent ) if both inputs are valid and RMISSD ( -9999.0 ) otherwise - * @throws NullPointerException - * - */ -public static final Amount prRelh ( Amount tmpc, Amount dwpc ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prRelh:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - - double prrelh = GempakConstants.RMISSD; -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(dwpc); - - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( dwpc ) ) - return new Amount (NonSI.PERCENT ); - - tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); - dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); - - /* Find the vapor pressure */ - Amount e = prVapr ( dwpc ); - - if ( !checkNullOrInvalidValue( e ) ) - return new Amount ( NonSI.PERCENT ); - - /*Find the saturated vapor pressure*/ - Amount es = prVapr ( tmpc ); - - if ( !checkNullOrInvalidValue( es ) ) - return new Amount ( NonSI.PERCENT ); - - /*Calculate humidity*/ - prrelh = ( e.doubleValue()/es.doubleValue()) * 100; - - return new Amount(prrelh,NonSI.PERCENT); -} - - /** - * Computes the dewpoint (in Celsius) from the temperature ( in Celsius ) - * and the relative humidity ( in percent ). - * @param tmpc - the temperature ( in deg Celsius ) - * @param relh - the relative humidity ( in percent ) - * @return the dewpoint in ( deg Celsius), if both inputs are valid and the value of the vapor - * pressure computed is greater than ( 1* e^(-30)) - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prRhdp ( Amount tmpc, Amount relh) throws InvalidValueException, NullPointerException{ - //System.out.println("From prRhdp:"); - //System.out.println(" tmpc =" + tmpc.doubleValue()); - //System.out.println(" relh = " + relh.doubleValue()); - -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(relh); - - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( relh ) ) - return new Amount ( SI.CELSIUS ); - - tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); - relh = checkAndConvertInputAmountToExpectedUnits(relh, NonSI.PERCENT); - - /*Calculate saturation vapor pressure; test for existence*/ - Amount vaps = prVapr ( tmpc); - - if ( !checkNullOrInvalidValue(vaps) ) - return new Amount ( SI.CELSIUS ); - - /*Calculate vapor pressure*/ - double relativeHumidity = relh.doubleValue(); - double saturationVaporPressure = vaps.doubleValue(); - double vapr = relativeHumidity * saturationVaporPressure / 100; - - /*Calculate dewpoint. The VAPR test prevents LOG blowups*/ - double prrhdp = -191; - Amount dewpointAmount = null; - if ( vapr >= ( Math.pow(Math.E, -30) ) ){//legacy checks for 1.E-30 - prrhdp = ( double) ( 243.5 * ( Math.log(6.112) - Math.log(vapr) ) / ( Math.log(vapr) - Math.log(6.112) - 17.67 ) ); - - /* If the dew-point is less than -190 degrees C, it is treated as missing data - * Note: Legacy documents it but does not implement it. - * However, in CAVE, it was decided to implement it. - * */ - - if ( prrhdp < -190 ){ - System.out.println(" From prRhdp: dewpoint is less than -190 C"); - return new Amount( SI.CELSIUS ); - } - } - dewpointAmount = new Amount(prrhdp, SI.CELSIUS); - return dewpointAmount; - } - - - public static class RZLL //implements ISerializableObject - { - // @DynamicSerializeElement - private static RZLL rzll; - - /**Station latitude in degrees*/ - // @DynamicSerializeElement - Amount stltdg = null; - - /**Station longitude in degrees*/ - // @DynamicSerializeElement - Amount stlndg = null; - - /**Range in kilometers*/ - // @DynamicSerializeElement - Amount range = null; - - /**Geographic azimuth in radians*/ - // @DynamicSerializeElement - Amount azim = null; - - /**Height above the ground in kilometers*/ - // @DynamicSerializeElement - Amount hght = null; - - /**Latitude in degrees*/ - // @DynamicSerializeElement - Amount xlat = null; - - /**Longitude in degrees*/ - // @DynamicSerializeElement - Amount xlon = null; - - /** - * @return the xlat - */ - public Amount getXlat() { - return xlat; - } - /** - * @return the xlon - */ - public Amount getXlon() { - return xlon; - } - - - - private static RZLL getInstance(){ - if (rzll == null) - {rzll = new RZLL();} - return rzll; - } - - /** - * Computes the actual latitude/longitude given the station latitude/longitude, elevation - * and azimuth. - * It uses equations developed for use in the AOIPS radar. - * @param instltdg - Station latitude in degrees - * @param instlndg - Station longitude in degrees - * @param inrange - Range in kilometers - * @param inazim - Geographic azimuth in radians - * @param inhght - Height above ground in km - * @throws NullPointerException - * - */ - public void prRzll ( Amount instltdg,Amount instlndg,Amount inrange,Amount inazim,Amount inhght) throws InvalidValueException, NullPointerException{ - //System.out.println("From prRzll:"); - //System.out.println(" instltdg = " + instltdg.doubleValue()); - //System.out.println(" instlndg = " + instlndg.doubleValue()); - //System.out.println(" inrange = " + inrange.doubleValue()); - //System.out.println(" inazim = " + inazim.doubleValue()); - //System.out.println(" inhght = " + inhght.doubleValue()); - -// checkNullOrInvalidValue( instltdg ); -// checkNullOrInvalidValue( instlndg ); -// checkNullOrInvalidValue(inrange); -// checkNullOrInvalidValue(inazim); -// checkNullOrInvalidValue(inhght); - - if ( !checkNullOrInvalidValue( instltdg ) - || !checkNullOrInvalidValue( instlndg ) - || !checkNullOrInvalidValue( inrange ) - || !checkNullOrInvalidValue( inazim ) - || !checkNullOrInvalidValue( inhght )) - return; - - instltdg = checkAndConvertInputAmountToExpectedUnits( instltdg, NonSI.DEGREE_ANGLE ); - instlndg = checkAndConvertInputAmountToExpectedUnits( instlndg, NonSI.DEGREE_ANGLE ); - inrange = checkAndConvertInputAmountToExpectedUnits(inrange, SI.KILOMETER ); - inazim = checkAndConvertInputAmountToExpectedUnits(inazim, SI.RADIAN ); - inhght = checkAndConvertInputAmountToExpectedUnits(inhght, SI.KILOMETER ); - - this.stltdg = new Amount ( instltdg.doubleValue(), NonSI.DEGREE_ANGLE ); - this.stlndg = new Amount ( instlndg.doubleValue(), NonSI.DEGREE_ANGLE ); - this.range = new Amount ( inrange.doubleValue(), SI.KILOMETER ); - this.azim = new Amount ( inazim.doubleValue(), SI.RADIAN ); - this.hght = new Amount ( inhght.doubleValue(), SI.KILOMETER ); - - double hdr = GempakConstants.RMISSD; - double elev = GempakConstants.RMISSD; - double rad = GempakConstants.RMISSD; - double radp= GempakConstants.RMISSD; - - /*Convert the station lat/lon to radians*/ - Amount stlat = checkAndConvertInputAmountToExpectedUnits( stltdg, NonSI.DEGREE_ANGLE ); - Amount stlon = checkAndConvertInputAmountToExpectedUnits( stlndg, NonSI.DEGREE_ANGLE ); - - - /*Get the elevation angle*/ - hdr = ( range.doubleValue() == 0.0f ? 0.0f : hght.doubleValue() / range.doubleValue() ); -// elev = (float) ( Math.abs(hdr) < 1.0f ? Math.asin(hdr) : 0.0f ); - elev = ( Math.abs(hdr) <= 1.0f ? Math.asin(hdr) : 0.0f ); - - double temp = (Math.pow( Math.sin(stlat.doubleValue()), 2 ) ) ; - - /*Get the earth's corrected radius*/ - rad = (6378.4 / Math.sqrt( 1 + ( 0.00677 * temp ) )); - radp = 4* ( rad / 3 ); - - double dist = GempakConstants.RMISSD; - double cx = GempakConstants.RMISSD; - double cy = GempakConstants.RMISSD; - double mathFormula1 = GempakConstants.RMISSD; - double mathFormula2 = GempakConstants.RMISSD; - - /*Calculate the distance*/ - double rangeVal = range.doubleValue(); - if ( elev > 0.2618f ) - dist = ( double ) ( rangeVal * Math.cos( elev )); - else{ - mathFormula1 = ( double ) ( (1 - ( Math.pow( elev, 2 ) / 2 ) ) - rangeVal * elev / radp); - dist = rangeVal * mathFormula1; - } - - /*Calculate the latitude and longitude*/ - double azimVal = azim.doubleValue(); - cx = ( double ) ( dist * Math.sin( azimVal ) ); - cy = ( double ) ( dist * Math.cos( azimVal ) ); - -// mathFormula2 = ( float ) ( ( ( 2 * Math.pow( rad, 2 ) ) * Math.tan( stlat ) )); -// xlat = ( float ) ( stlat + ( cy / rad ) - ( Math.pow(cx, 2) / mathFormula2 ) ); - double stlatVal = stlat.doubleValue(); - mathFormula2 = (double) ( ( Math.pow(cx, 2) / ( 2 * Math.pow( rad, 2 ) ) * Math.tan( stlatVal ) )); - double xlatVal = ( double ) ( stlatVal + ( cy / rad ) - mathFormula2 ); - double xlonVal = ( double ) ( stlon.doubleValue() + ( cx / ( rad * Math.cos( xlat.doubleValue() ) ) ) ) ; - - /*Change lat/lon to degrees*/ - xlatVal = SI.RADIAN.getConverterTo(NonSI.DEGREE_ANGLE).convert( xlatVal ); - xlonVal = SI.RADIAN.getConverterTo(NonSI.DEGREE_ANGLE).convert( xlonVal ); - - this.xlat = new Amount ( xlatVal, NonSI.DEGREE_ANGLE ); - this.xlon = new Amount ( xlonVal, NonSI.DEGREE_ANGLE ); - } - } - - - /** - * Computes the wind speed from the 'U' and 'V' components of - * the wind velocity. The formula is the square root of ( u^2 + v^2 ) - * - * @param uWnd - U component of velocity - * @param vWnd - V component of velocity - * @return the computed windspeed if both inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prSped ( Amount uWnd, Amount vWnd) throws InvalidValueException, NullPointerException{ - //System.out.println("From prSped:"); - //System.out.println(" uWnd = " +uWnd.doubleValue()); - //System.out.println(" vWnd = " + vWnd.doubleValue()); - -// checkNullOrInvalidValue( uWnd ); -// checkNullOrInvalidValue( vWnd ); - if ( !checkNullOrInvalidValue( uWnd ) - || !checkNullOrInvalidValue( vWnd ) ) - return new Amount ( uWnd.getUnit() ); - - Unit uWndUnits = uWnd.getUnit(); - Unit vWndUnits = vWnd.getUnit(); - if ( uWndUnits != vWndUnits && uWndUnits.isCompatible(vWndUnits)){ - double vWndVal = vWndUnits.getConverterTo( uWndUnits ).convert( vWnd.doubleValue() ); - vWnd = new Amount ( vWndVal, uWndUnits ); - } - double prsped = ( Math.sqrt( ( Math.pow(uWnd.doubleValue(), 2) + Math.pow(vWnd.doubleValue(), 2) ) ) ); - return new Amount ( prsped, uWndUnits ); - } - - /** - * Computes the potential temperature ( in Kelvin ) from the - * temperature (in Celsius ) and the pressure ( in mb ). - * @param tmpc - The temperature ( in Celsius ) - * @param pres - The pressure ( in mb ) - * @return the potential temperature ( in Kelvin ), if both inputs are valid - * . - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static Amount prThta ( Amount tmpc, Amount pres) throws InvalidValueException, NullPointerException{ - checkNullOrInvalidValue(tmpc); - checkNullOrInvalidValue(pres); - - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( pres )) - return new Amount ( SI.KELVIN ); - - tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - double pressureValue = pres.doubleValue(); - if ( pressureValue <= 0 ){ - System.out.println("From prThta( ) - pressure must be > 0 "); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException("From prThta( ) - pressure must be > 0 "); - } - - /*Change temperature in degrees Celsius to Kelvin.*/ - double temperatureInKelvin = (double) tmpc.getUnit().getConverterTo(SI.KELVIN).convert(tmpc.doubleValue() ); - - /*Calculate theta using Poisson's equation*/ - double prthta = ( double ) ( temperatureInKelvin * Math.pow( ( 1000 / pres.doubleValue()) , GempakConstants.RKAPPA) ); - return new Amount ( prthta , SI.KELVIN ); - } - - - /** - * Computes the equivalent potential temperature ( in Kelvin ) from - * the pressure ( in mb ), the temperature ( in Celsius ) and the dewpoint ( in Celsius ) - * using the equation: - * THTE = THTAM * EXP [ ( 3.376/TLCL - .00254 ) * - * ( MIXR * ( 1 + .81*.001*MIXR ) ) ] - * where - * THTAM = potential temperature of moist air - * = TMPK * (1000 / PRES) ** E - * E = RKAPPA * ( 1 - ( .28 * .001 * MIXR ) ) - * Bolton. - * - * @param pres - the pressure ( in mb ) - * @param tmpc - the temperature ( in Celsius ) - * @param dwpc - the dewpoint ( in Celsius ) - * @return the the equivalent potential temperature ( in Kelvin ), if all the input values - * are valid - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prThte ( Amount pres, Amount tmpc, Amount dwpc ) { - //System.out.println("From prThte:"); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - -// checkNullOrInvalidValue(pres); -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(dwpc); - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( dwpc ) ) - return new Amount ( SI.KELVIN ); - - if ( pres.doubleValue() <= 0){ - System.out.println("From prThte() - Input pressure must be greater than 0 "); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException("From prThte() - Input pressure must be greater than 0 "); - } - - pres = checkAndConvertInputAmountToExpectedUnits( pres, NcUnits.MILLIBAR ); - dwpc = checkAndConvertInputAmountToExpectedUnits( dwpc, SI.CELSIUS ); - tmpc = checkAndConvertInputAmountToExpectedUnits( tmpc, SI.CELSIUS ); - - /*Find mixing ratio*/ - Amount rmix = prMixr ( dwpc, pres); - if ( !checkNullOrInvalidValue(rmix) ) - return new Amount ( SI.KELVIN ); - /*Change degrees Celsius to Kelvin*/ - Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); - - - /*Calculate theta for moist air (thtam) */ - double mixingRatioVal = rmix.doubleValue(); - double pressureVal = pres.doubleValue(); - double tempVal = tmpk.doubleValue(); - - double e = ( double) ( GempakConstants.RKAPPA * (1 - (0.28 * 0.001 * mixingRatioVal) )); - double thtam = ( double ) ( tempVal * Math.pow(1000 / pressureVal, e)); - - /*Find the temperature at the lifted condensation level */ - Amount tlcl = prTlcl ( tmpc, dwpc ); - - if ( ! checkNullOrInvalidValue(tlcl) ) - return new Amount ( SI.KELVIN ); - - double lclTemp = tlcl.doubleValue(); - e = ( ( 3.376f / lclTemp ) - 0.00254f ) * ( mixingRatioVal * ( 1 + 0.81f * 0.001f * mixingRatioVal )); - double prthte = ( double ) ( thtam * Math.exp(e)); - Amount equivPotentialTempAmount = new Amount(prthte, SI.KELVIN); - return equivPotentialTempAmount; - } - - /** - * Computes wet bulb potential temperature ( in Celsius ) from the - * pressure, temperature and dewpoint. The result is obtained by first - * computing the equivalent potential temperature (thte) of the the air parcel at level - * pres. Then the air parcel is brought to 1000 mb moist adiabatically to get the - * wet bulb potential temperature. - * @param pres - Pressure ( in millibars ) - * @param tmpc - Temperature ( in Celsius ) - * @param dwpc - Dewpoint ( in Celsius ) - * @return The wet bulb potential temperature ( in Celsius ) if all inputs are valid - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prThwc ( Amount pres, Amount tmpc, Amount dwpc) throws InvalidValueException, NullPointerException { - //System.out.println("From prThwc:"); - //System.out.println(" press = " + pres.doubleValue()); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - - /*Check for missing and invalid data*/ -// checkNullOrInvalidValue( pres ); -// checkNullOrInvalidValue( tmpc ); -// checkNullOrInvalidValue( dwpc ); - - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( dwpc ) ) - return new Amount ( SI.CELSIUS ); - - pres = checkAndConvertInputAmountToExpectedUnits( pres , NcUnits.MILLIBAR ); - tmpc = checkAndConvertInputAmountToExpectedUnits( tmpc , SI.CELSIUS ); - dwpc = checkAndConvertInputAmountToExpectedUnits( dwpc , SI.CELSIUS ); - double presVal = pres.doubleValue(); - if ( presVal <= 0 ){ - System.out.println("From prThwc: Pressure must be greater than 0 mb"); - return new Amount ( SI.CELSIUS ); -// throw new InvalidRangeException("From prThwc: Pressure must be greater than 0 mb"); - } - /*Compute the thte*/ - Amount thte = prThte(pres, tmpc, dwpc); - - /*Check for missing 'thte' and compute wet bulb temperature.*/ - if ( ! checkNullOrInvalidValue( thte ) ) - return new Amount ( SI.CELSIUS ); - /*Compute the parcel temperature (in Kelvin)*/ - - Amount prthwc = prTmst ( thte, - new Amount ( 1000, NcUnits.MILLIBAR ), - new Amount ( 0, SI.KELVIN ) ); - if ( ! checkNullOrInvalidValue( prthwc ) ) - return new Amount ( SI.CELSIUS ); - /*Convert the parcel temperature to Celsius*/ - prthwc = checkAndConvertInputAmountToExpectedUnits(prthwc, SI.CELSIUS); - - return prthwc; - } - - - /** - * Computes the temperature at the lifted condensation level for a parcel of air - * given the temperature ( in Celsius ) and the dewpoint (in Celsius) using the - * equation: - * TLCL = [ 1 / ( 1 / (DWPK-56) + ALOG (TMPK/DWPK) / 800 ) ] + 56 - * Bolton. - * @param tmpc - the temperature ( in Celsius ) - * @param dwpc - the dewpoint ( in Celsius ) - * @return the lifted condensation level temperature In Kelvin, if both input values are valid - * - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prTlcl ( Amount tmpc, Amount dwpc ) { - //System.out.println("From prTlcl:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - - -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(dwpc); - if ( !checkNullOrInvalidValue( tmpc ) - || !checkNullOrInvalidValue( dwpc ) ) - return new Amount ( SI.KELVIN ); - - if ( tmpc.doubleValue() < -GempakConstants.TMCK || dwpc.doubleValue() < -GempakConstants.TMCK){ - System.out.println("From prTlcl: Input temperature cannot be less than -273.15"); - return new Amount (SI.KELVIN ); - } - Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); - Amount dwpk = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.KELVIN); - double tempVal = tmpk.doubleValue(); - double dewpointVal = dwpk.doubleValue(); - double lclTemp = ( double ) ( ( 800 * ( dewpointVal - 56 ) / ( 800 + ( dewpointVal - 56 ) * Math.log ( tempVal / dewpointVal ) ) ) + 56 ); - Amount prtlcl = new Amount ( lclTemp, SI.KELVIN ); - - return prtlcl; - } - - /** - * Computes the temperature ( in Kelvin ) from the pressure ( in mb ) and - * the potential temperature ( in Kelvin ) using the Poisson equation: - * TMPK = THTA * ( PRES / 1000 ) ** RKAPPA - * - * @param pres - the pressure ( in mb ) - * @param thta - the potential temperature ( in Kelvin ) - * @return the temperature ( in Kelvin ) - * @throws InvalidRangeException - * @throws NullPointerException - * - */ - public static final Amount prTmpk ( Amount pres, Amount thta ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prTmpk:"); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println("thta = " + thta.doubleValue()); - - Amount prtmpk = new Amount ( SI.KELVIN ); - if ( !checkNullOrInvalidValue(pres) - || !checkNullOrInvalidValue(thta) ){ - return new Amount ( SI.KELVIN ); - } - - pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); - thta = checkAndConvertInputAmountToExpectedUnits(thta, SI.KELVIN); - double pressureValue = pres.doubleValue(); - double thtaValue = thta.doubleValue(); - if ( pressureValue >= 0){ - double temperature = ( double ) ( thtaValue * ( Math.pow( pressureValue / 1000f, GempakConstants.RKAPPA ) ) ); - prtmpk = new Amount ( temperature , SI.KELVIN ); - return prtmpk; - } - else{ - System.out.println("From prTmpk() - pressure cannot be less than 0 mb"); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException("From prTmpk() - pressure cannot be less than 0 mb"); - } - } - - /** - *
-	 * Computes the parcel temperature ( in Kelvin ) from the equivalent potential temp ( in Kelvin ),
-	 * pressure ( in millibars ) and the first guess temperature ( in Kelvin ). 
-	 * The parcel temperature at level pres on a specified moist adiabat ( thte ). 
-	 * The computation is an iterative Newton-Raphson technique of the form:
-	 * 
-	 * x = x(guess) + [ f( x ) - f( x(guess) ) ] / f'( x(guess) )
-	 * f' is approximated with finite differences
-	 * f' = [ f( x(guess) + 1 ) - f( x(guess) ) ] / 1
-	 * 
-	 * If tguess is 0, a reasonable first guess will be made.
-	 * Convergence is not guaranteed for extreme input values.  If the
-	 * computation does not converge after 100 iterations, the missing 
-	 * data value will be returned. 
-	 * @param thte      - Equivalent potential temp ( in Kelvin )
-	 * @param pres      - Pressure ( in millibars )
-	 * @param tguess   - First guess temperature ( in Kelvin )
-	 * @return the Parcel temperature in Kelvin if all the input values are valid 
-	 * (without being extreme) and if a convergence is obtained within 100 iterations 
-	 * 
- * @throws NullPointerException - * - */ - public static final Amount prTmst ( Amount thte, Amount pres, Amount tguess) { - double prtmst = GempakConstants.RMISSD; - //System.out.println("From prTmst:"); - //System.out.println(" thte = " + thte.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - //System.out.println(" tguess = " + tguess.doubleValue()); - if ( !checkNullOrInvalidValue(pres) - || !checkNullOrInvalidValue(thte) - || !checkNullOrInvalidValue(tguess) ){ - return new Amount ( SI.KELVIN ); - } -// checkNullOrInvalidValue( thte ); -// checkNullOrInvalidValue( pres ); -// checkNullOrInvalidValue( tguess ); - thte = checkAndConvertInputAmountToExpectedUnits( thte, SI.KELVIN ); - pres = checkAndConvertInputAmountToExpectedUnits( pres, NcUnits.MILLIBAR ); - tguess = checkAndConvertInputAmountToExpectedUnits( tguess, SI.KELVIN ); - double thteVal = thte.doubleValue(); - double presVal = pres.doubleValue(); - double tguessVal = tguess.doubleValue(); - - if ( thteVal <= 0 ){ - System.out.println(" From prTmst(): Potential temperature must be greater than 0"); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException(" From prTmst(): Potential temperature must be greater than 0"); - } - else if ( presVal <= 0 ){ - System.out.println(" From prTmst(): Pressure must be greater than 0"); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException(" From prTmst(): Pressure must be greater than 0"); - } - else if ( tguessVal < 0 ){ - System.out.println(" From prTmst(): First guess temperature must be greater than or equal to 0"); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException(" From prTmst(): First guess temperature must be greater than 0"); - } - double tg = tguess.doubleValue(); - /* - * If tguess is passed as 0. it is computed from an MIT scheme - */ - if ( tg == 0 ){ - double diffVar = thte.doubleValue() - 270; - double mathFormula1 = (double) ( diffVar > 0 ? diffVar : 0.0 ); - tg = (double) ( ( thte.doubleValue() - 5.0f * ( Math.pow ( mathFormula1, 1.05f) ) ) - * ( Math.pow ( pres.doubleValue() / 1000.0f, 0.2f ) ) ); - } - - /*Set convergence and initial guess in degrees Celsius*/ - double epsi = 0.01f; - double tgnu = SI.KELVIN.getConverterTo(SI.CELSIUS).convert(tg) ; - - /* - * Set a limit of 100 iterations. Compute tenu,tenup, the - * thte's at one degree above the guess temperature. - */ - int index = 0; - while( index < 100 ){ - double tgnup = tgnu + 1; - Amount tgnuAmount = new Amount ( tgnu, SI.CELSIUS ); - Amount tgnupAmount = new Amount ( tgnup, SI.CELSIUS ); - Amount tenu = prThte ( pres, tgnuAmount, tgnuAmount ); - Amount tenup = prThte ( pres, tgnupAmount, tgnupAmount ); - /*Check that the THTE's exist.*/ - - if (( ! checkNullOrInvalidValue(tenu) - || !checkNullOrInvalidValue(tenup) ) ){ - return new Amount ( SI.KELVIN); -// index++; -// continue; - } - - /*Compute the correction*/ - double tenuVal = tenu.doubleValue(); - double tenupVal = tenup.doubleValue(); - double cor = ( thteVal - tenuVal ) / ( tenupVal - tenuVal ); - tgnu += cor; - - if ( ( cor < epsi ) && (-cor < epsi) ){ - - /*return on convergence*/ - prtmst = tgnuAmount.getUnit().getConverterTo(SI.KELVIN).convert(tgnu); - break; - } - - index++; - } - return new Amount ( prtmst, SI.KELVIN); - } - - /** - *
-	 * Computes wet bulb temperature from the temperature, mixing ratio, and pressure.
-	 * The result is obtained by solving for the temperature at which saturation occurs,
-	 *  when the latent heat required to vaporize the water is provided by a cooling of the air.
-	 *  The equation representing the process is:
-	 *   ( tmpk - tmwb ) * cp - ( Rsat (tmwb) - rmix ) * lvap = 0  
-	 *  This implicit equation is solved by Newton's method, since the 
-	 *  saturation mixing ratio Rsat is a transcendental function of tmwb.
-	 *  The expressions for the heat of vaporization (LVAP) and saturation 
-	 *   vapor pressure are equations (2) and (10) from Bolton (MWR, 1980).
-	 *   
- * @param tmpk - Temperature (K) - * @param rmix - Mixing ratio (g/kg) - * @param pres - Pressure (mb) - * @return Wet bulb temperature (K) if all inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prTmwb ( Amount tmpk, Amount rmix, Amount pres) throws InvalidValueException, NullPointerException{ - //System.out.println("From prTmwb:"); - //System.out.println(" tmpk = " + tmpk.doubleValue()); - //System.out.println(" rmix = " + rmix.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - - Amount prtmwb = null; - /*Check for missing and invalid data*/ -// checkNullOrInvalidValue( tmpk ); -// checkNullOrInvalidValue( rmix ); -// checkNullOrInvalidValue( pres ); - if ( !checkNullOrInvalidValue(pres) - || !checkNullOrInvalidValue(tmpk) - || !checkNullOrInvalidValue(rmix) ){ - return new Amount ( SI.KELVIN ); - } - tmpk = checkAndConvertInputAmountToExpectedUnits( tmpk, SI.KELVIN ); - rmix = checkAndConvertInputAmountToExpectedUnits( rmix , NcUnits.GRAMS_PER_KILOGRAM ); - pres = checkAndConvertInputAmountToExpectedUnits( pres , NcUnits.MILLIBAR ); - double presVal = pres.doubleValue(); - if ( presVal <= 0 ){ - System.out.println("From prTmwb - pressure value must be greater than 0 "); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException("From prTmwb - pressure value must be greater than 0 "); - } - /*Change temperature to degrees Celsius.*/ - Amount tmp = checkAndConvertInputAmountToExpectedUnits(tmpk, SI.CELSIUS); - - /*Compute the latent heat of vaporization.*/ - Amount lvap = prLhvp ( tmp ); - if ( !checkNullOrInvalidValue( lvap ) ) - return new Amount ( SI.KELVIN ); - /*Compute the specific heat of moist air*/ - double rmixVal = rmix.doubleValue()/1000; - double cp = ( 1005.7 * ( 1.0 + 0.887 * rmixVal ) ); - - double rlocp = lvap.doubleValue() / cp; - - /*Do Newton iteration*/ - int iter = 0; - double twb = tmp.doubleValue(); - boolean isConvrg = false; - - double A = 6.112; - double B = 17.67; - double C = 243.5; - double EPSI = 0.622; - double G = B * C; - double ERRMAX = 0.001; - double tmpVal = tmp.doubleValue(); - while ( iter <= 50 && !isConvrg ){ - iter++; - double bt = B * twb; - double tpc = twb + C; - double d = ( ( presVal / A ) * Math.exp( ( -bt ) / tpc ) ); - double dm1 = d - 1; - double f = (tmpVal - twb ) - rlocp * ( EPSI / dm1 - rmixVal ); - double df = (-G) / ( tpc * tpc ); - df = d * df * rlocp * EPSI / ( dm1 * dm1 ) - 1; - double cor = f / df; - twb = twb - cor; - if ( Math.abs ( cor ) <= ERRMAX ) - isConvrg = true; - } - - if ( isConvrg ){ - Amount twk = new Amount ( twb , SI.KELVIN ); - if ( twk.doubleValue() > tmpk.doubleValue() ) - twk = new Amount ( tmpk.doubleValue(), SI.KELVIN ) ; - - prtmwb = twk; - } -// } - return prtmwb; - } - - - /** - * Computes the virtual temperature ( in Kelvin ) from the temperature ( in Celsius ), - * dewpoint ( in Celsius ) and pressure ( in mb ) where DWPC and PRES are used to compute - * MIXR. The following equation is used: - * TVRK = TMPK * (1 + .001 * MIXR / .62197) / (1 + .001 * MIXR) - * If DWPC is missing, dry air is assumed and TMPK is returned. - * - * @param tmpc - Temperature ( in Celsius ) - * @param dwpc - Dewpoint ( in Celsius ) - * @param pres - Pressure ( in mb ) - * @return the virtual temperature ( in Kelvin ) - * @throws NullPointerException - * - */ - public static final Amount prTvrk( Amount tmpc, Amount dwpc, Amount pres ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prTvrk:"); - //System.out.println(" tmpc = " + tmpc.doubleValue()); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - - Amount prtvrk = null; -// checkNullOrInvalidValue(tmpc); -// checkNullOrInvalidValue(pres); - - if ( !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( tmpc ) ){ - return new Amount ( SI.KELVIN ); - } - - /*If dewpoint is missing, return temperature*/ - - if ( !checkNullOrInvalidValue(dwpc )) - return checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); - - else { - /*Change temperature to Kelvin.*/ - Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); - - /* Find mixing ratio in g/kg; if missing, return temperature */ - Amount rmix = prMixr ( dwpc, pres ); - - double virtualTemp; - if (rmix.doubleValue() == GempakConstants.RMISSD ) - virtualTemp = (double) tmpc.getUnit().getConverterTo(SI.KELVIN).convert(tmpc.doubleValue()); - else{ - double mixingRatioVal = rmix.doubleValue(); - double temp = tmpk.doubleValue(); - virtualTemp =( double ) ( temp * ( 1 + 0.001 * mixingRatioVal / 0.62197 ) / ( 1 + 0.001 * mixingRatioVal ) ); - - } - prtvrk = new Amount (virtualTemp, SI.KELVIN); - } - return prtvrk; - } - - /** - * Computes the 'U' component of the wind from its speed and direction - * @param sped - wind speed - * @param drct - wind direction - * @return The 'U' component of the wind if both inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prUwnd ( Amount sped, Amount drct) throws InvalidValueException, NullPointerException { - //System.out.println("From prUwnd:"); - //System.out.println(" sped = " + sped.doubleValue()); - //System.out.println(" drct = " + drct.doubleValue()); - if ( !checkNullOrInvalidValue( drct ) - || !checkNullOrInvalidValue( sped ) ){ - return new Amount (SI.METERS_PER_SECOND ); - } - drct = checkAndConvertInputAmountToExpectedUnits(drct, NonSI.DEGREE_ANGLE ); - double pruwnd = ( ( -Math.sin( drct.doubleValue() * GempakConstants.DTR ) ) * sped.doubleValue() ); - return new Amount ( pruwnd, sped.getUnit() ); //TODO :verify the units - } - - /** - * Computes the 'V' component of the wind from its speed and direction - * @param sped - wind speed - * @param drct - wind direction - * @return The 'V' component of the wind if both inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prVwnd ( Amount sped, Amount drct) throws InvalidValueException, NullPointerException { - //System.out.println("From prVwnd:"); - //System.out.println(" sped = " + sped.doubleValue()); - //System.out.println(" drct = " + drct.doubleValue()); - if ( !checkNullOrInvalidValue( drct ) - || !checkNullOrInvalidValue( sped ) ){ - return new Amount (SI.METERS_PER_SECOND ); - } - drct = checkAndConvertInputAmountToExpectedUnits(drct, NonSI.DEGREE_ANGLE ); - double prvwnd = ( ( -Math.cos( drct.doubleValue() * GempakConstants.DTR ) ) * sped.doubleValue() ); - return new Amount ( prvwnd, sped.getUnit() ); //TODO :verify the units - } - - - /** - * Computes the vapor pressure ( in mb) from the input - * dewpoint temperature in Celsius using the equation: - * VAPR = 6.112 * EXP [ (17.67 * dewpointValue) / (dewpointValue + 243.5) ] - * @param dwpc - the dewpoint temperature ( in Celsius ) - * @return the vapor pressure ( in mb) from the dewpoint temperature if it is valid - * @throws NullPointerException - * - */ - public static final Amount prVapr ( Amount dwpc) { - //System.out.println("From prVapr:"); - //System.out.println(" dwpc = " + dwpc.doubleValue()); - - if ( !checkNullOrInvalidValue(dwpc) ) - return new Amount ( NcUnits.MILLIBAR ); - dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); - double dewpointValue = dwpc.doubleValue(); - if ( dewpointValue >= -240.0f ) - return ( new Amount ( ( 6.112 * ( Math.exp ( ( 17.67 * dewpointValue ) / ( dewpointValue + 243.5) ) ) ) , - NcUnits.MILLIBAR ) ); - else{ -// throw new InvalidRangeException("Exception from prVapr() - dewpoint cannot be less than -240 "); - System.out.println("From prVapr() - dewpoint cannot be less than -240 "); - return new Amount ( NcUnits.MILLIBAR ); - } - } - -// /** -// * Computes the visibility ( in nautical miles ) from the input visibility ( in kilometers ) -// * @param vsbk - visibility ( in kilometers ) -// * @return visibility ( in nautical miles ) if the input is valid -// * @throws NullPointerException -// * -// */ -// //TODO: remove this - since the getValueAs(Unit) offers the same facility? -// public static final Amount prVskn ( Amount vsbk) throws InvalidValueException, NullPointerException { -// //System.out.println("From prVskn:"); -// //System.out.println(" vsbk = " + vsbk.doubleValue()); -// -// checkNullOrInvalidValue( vsbk ); -// vsbk = checkAndConvertInputAmountToExpectedUnits( vsbk, SI.KILOMETER ); -// return ( checkAndConvertInputAmountToExpectedUnits(vsbk, NonSI.NAUTICAL_MILE ) ); -// } - - /** - * Computes the wind chill equivalent temperature ( the temperature with calm winds that produces the same - * cooling effect as the given temperature with the given wind speed) - * @param tmpf - Air temperature ( in Farenheit ) - * @param sknt - Wind speed ( in knots ) - * @return the wind chill equivalent temperature ( in Farenheit ), - * if the inputs are valid - * @throws NullPointerException - * - */ - public static final Amount prWceq ( Amount tmpf, Amount sknt ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prWceq:"); - //System.out.println(" tmpf = " + tmpf.doubleValue()); - //System.out.println(" sknt = " + sknt.doubleValue()); - - double prwceq = GempakConstants.RMISSD; - if ( !checkNullOrInvalidValue( tmpf ) - || !checkNullOrInvalidValue( sknt ) ){ - return new Amount (NonSI.FAHRENHEIT ); - } - - /*Convert input variables to Celsius and meters/second.*/ - Amount tmpc = checkAndConvertInputAmountToExpectedUnits(tmpf, SI.CELSIUS); - Amount sped = checkAndConvertInputAmountToExpectedUnits(sknt, SI.METERS_PER_SECOND) ; - - - if ( sped.doubleValue() <= 1.34) - /*If the wind speed does not exceed 1.34 m/s ( not much wind to contribute to the wind chill), - * return the input temperature as the wind chill temperature*/ - prwceq = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT).convert(tmpc.doubleValue()); - else{ - /* - * Compute the wind chill temp if the inputs are not missing and - * and the wind speed is greater than 1.34 m/s. - * Equations for wind chill computation - * from R. Falconer, "Windchill, A Useful Wintertime Weather Variable", Weatherwise, Dec 1968. - */ - if ( sped.getUnit() == SI.METERS_PER_SECOND ){ - float windChill = ( float ) ( 33.0 - ( ( 33.0 - tmpc.doubleValue() ) * wci( sped.doubleValue() ) / wci( 1.34f ) ) ); - prwceq = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT).convert(windChill); - } - } - - return ( new Amount ( prwceq, NonSI.FAHRENHEIT)); - } - - -// /** -// * Computes the numeric total cloud cover for the worst case aviation flight condition, -// * based on the categorical identification of flight rules for prevailing and temporary / probability conditions. -// * @param xvfr - Prevailing categorical id of flight rules -// * @param txvf - Temporary / Probability categorical id of flight rules -// * @param cfrt - Prevailing numeric total cloud cover -// * @param tcfr - Temporary / Probability numeric total cloud cover -// * @return Worst case numeric total cloud cover or RMISSD (-9999) if the computation does not fall through -// * @throws NullPointerException -// * -// */ -// public static final Amount prWcfr ( Amount xvfr, Amount txvf, Amount cfrt, Amount tcfr) throws InvalidValueException, NullPointerException { -// double prwcfr = GempakConstants.RMISSD; -// checkNullOrInvalidValue( tcfr ); -// checkNullOrInvalidValue( cfrt ); -// double cfrtVal = cfrt.doubleValue(); -// double tcfrVal = tcfr.doubleValue(); -// if ( ( xvfr == null || xvfr.doubleValue() == GempakConstants.RMISSD ) -// || ( txvf == null || txvf.doubleValue() == GempakConstants.RMISSD )) -// prwcfr = ( cfrtVal > tcfrVal ? cfrtVal : tcfrVal ); -// -// else { -// double txvfVal = txvf.doubleValue(); -// double xvfrVal = xvfr.doubleValue(); -// if ( txvfVal < xvfrVal ) -// prwcfr = tcfrVal; -// else if ( txvfVal == xvfrVal) -// prwcfr = ( cfrtVal > tcfrVal ? cfrtVal : tcfrVal ); -// else -// prwcfr = cfrtVal; -// } -// return ( new Amount ( prwcfr ,Unit.ONE)); -// } - - - /** - * Computes the wind chill temperature from the air temperature and the wind speed - * @param tmpf - Air temperature ( in degree Farenheit ) - * @param sknt - Wind speed ( in knots ) - * @return the wind chill temperature ( in Farenheit ) if none of the inputs are missing - * @throws NullPointerException - * - */ - public static final Amount prWcht ( Amount tmpf, Amount sknt) throws InvalidValueException, NullPointerException{ - //System.out.println("From prWcht:"); - //System.out.println(" tmpf = " + tmpf.doubleValue()); - //System.out.println(" sknt = " + sknt.doubleValue()); - double prwrcht = GempakConstants.RMISSD; - if ( !checkNullOrInvalidValue( tmpf ) - || !checkNullOrInvalidValue( sknt ) ){ - return new Amount (NonSI.FAHRENHEIT ); - } - - /*Convert the speed to miles per hour*/ - Amount smph = checkAndConvertInputAmountToExpectedUnits(sknt, NonSI.MILES_PER_HOUR); - - /*If the inputs are not missing , check if the wind speed is <= 3 miles per hour*/ - - double smphVal = smph.doubleValue(); - double tmpfVal = tmpf.doubleValue(); - if( smphVal <= 3 ) - prwrcht = tmpfVal ; - else{ - /*Compute the wind-chill temperature for wind speeds that exceed 3 miles per hour*/ - float wcht = ( float ) ( 35.74 + 0.6215 * tmpfVal -35.75 * Math.pow(smphVal, 0.16) - + 0.4275 * tmpfVal * Math.pow(smphVal, 0.16) ); - prwrcht = ( wcht > tmpfVal ? tmpfVal : wcht); - } - return ( new Amount ( prwrcht , NonSI.FAHRENHEIT ) ); - } - - - /** - * Computes the wind component towards a specific direction from the - * wind direction, wind speed and direction of desired component. - * @param drct - the wind direction in degrees - * @param sped - the wind speed in m/s - * @param dcmp - the direction of the desired component - * @return the component of the wind (in m/s) if none of the input parameters are missing - * @throws NullPointerException - * - */ - public static final Amount prWcmp ( Amount drct, Amount sped, Amount dcmp ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prWcmp:"); - //System.out.println(" sped = " + sped.doubleValue()); - //System.out.println(" drct = " + drct.doubleValue()); - //System.out.println(" dcmp = " + dcmp.doubleValue()); - /*Check for missing input parameters*/ -// checkNullOrInvalidValue( drct ); -// checkNullOrInvalidValue( sped ); -// checkNullOrInvalidValue( dcmp ); - if ( !checkNullOrInvalidValue( drct ) - || !checkNullOrInvalidValue( sped ) - || !checkNullOrInvalidValue( dcmp ) ){ - return new Amount (SI.METERS_PER_SECOND ); - } - - drct = checkAndConvertInputAmountToExpectedUnits( drct , NonSI.DEGREE_ANGLE ); - dcmp = checkAndConvertInputAmountToExpectedUnits( dcmp , NonSI.DEGREE_ANGLE ); - sped = checkAndConvertInputAmountToExpectedUnits( sped , SI.METERS_PER_SECOND ); - - /*Calculate wind speed toward specified direction*/ - double prwcmp = sped.doubleValue() * ( -Math.cos( ( drct.doubleValue() - dcmp.doubleValue()) *GempakConstants.DTR ) ) ; - - return new Amount ( prwcmp , SI.METERS_PER_SECOND); - } - - /** - * Computes the wind component toward a direction 90 degrees counterclockwise of a specified direction. - * If no direction is specified, the component toward north is returned. - * @param drct - wind direction ( in degrees ) - * @param sped - wind speed ( in knots or m/s ) - * @param dcmp - specified wind direction ( in degrees ) - * @return a component of the wind in m/s if the input wind speed and direction are valid and if - * the specified wind direction is between 0 degrees and 360 degrees. - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prWnml ( Amount drct, Amount sped, Amount dcmp) throws InvalidValueException, NullPointerException{ - //System.out.println("From prWnml:"); - //System.out.println(" sped = " + sped.doubleValue()); - //System.out.println(" drct = " + drct.doubleValue()); - //System.out.println(" dcmp = " + dcmp.doubleValue()); -// checkNullOrInvalidValue(sped); -// checkNullOrInvalidValue(drct); -// checkNullOrInvalidValue(dcmp); - if ( !checkNullOrInvalidValue( drct ) - || !checkNullOrInvalidValue( sped ) - || !checkNullOrInvalidValue( dcmp ) ){ - return new Amount (SI.METERS_PER_SECOND ); - } - - drct = checkAndConvertInputAmountToExpectedUnits( drct , NonSI.DEGREE_ANGLE ); - dcmp = checkAndConvertInputAmountToExpectedUnits( dcmp , NonSI.DEGREE_ANGLE ); - sped = checkAndConvertInputAmountToExpectedUnits( sped , SI.METERS_PER_SECOND ); - if ( ( dcmp.doubleValue() < 0 ) && ( dcmp.doubleValue() > 360 ) ){ -// throw new InvalidRangeException("From prWnml - the wind direction 'dcmp' mus be greater than or equal to 0 and less than or equal to 360"); - System.out.println("From prWnml - the wind direction 'dcmp' mus be greater than or equal to 0 and less than or equal to 360"); - return new Amount ( SI.METERS_PER_SECOND); - } - /* - * Calculate wind speed 90 degrees to left of given direction. - */ - double prwnml = ( float ) ( sped.doubleValue() * ( -Math.cos( ( drct.doubleValue() - dcmp.doubleValue() - 90 ) * GempakConstants.DTR ) ) ); - return ( new Amount ( prwnml , SI.METERS_PER_SECOND ) ); - } - -// /** -// * Computes the packed wind speed and direction from the input wind speed and wind direction -// * @param drct - wind direction ( in degrees ) -// * @param sped - wind speed ( in knots or m/s ) -// * @return the packed speed and direction -// */ -// public static final Amount prWind ( Amount drct, Amount sped ){ -// float prwind = GempakConstants.RMISSD; -//// if ( !MissingValueTester.isDataValueMissing(drct) -//// && !MissingValueTester.isDataValueMissing(sped)){ -// /* -// * (Non-Javadoc) -// * The packed wind speed and direction are of the form: -// * SSSDDD, where SSS - wind speed ( in knots or m/s ) and -// * DDD - wind direction in degrees -// * -// */ -// int jdrct = (int ) Math.round( drct.doubleValue() ); -// int jsped = (int) Math.round( sped.doubleValue() ); -// prwind = jdrct + jsped * 1000; -//// } -// return prwind; -// } - - /** - * Computes the worst case categorical identification of flight rules for prevailing - * and temporary / probability conditions. - * @param xvfr - Prevailing categorical id of flight rules - * @param txvf - Temporary / probability categorical id of flight rules - * @return The worst case categorical id of flight rules - * @throws NullPointerException - * - */ - public static final Amount prWxvf ( Amount xvfr, Amount txvf ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prWxvf:"); - //System.out.println(" xvfr = " + xvfr.doubleValue()); - //System.out.println(" txvf = " + txvf.doubleValue()); - - double prwxvf = GempakConstants.RMISSD; - if ( txvf != null && xvfr != null ){ - - double xvfrVal = xvfr.doubleValue(); - double txvfVal = txvf.doubleValue(); - - if ( txvfVal != GempakConstants.RMISSD && xvfrVal != GempakConstants.RMISSD ) - prwxvf = ( xvfrVal < txvfVal ? xvfrVal : txvfVal ); - - else if (xvfrVal == GempakConstants.RMISSD && txvfVal != GempakConstants.RMISSD ) - prwxvf = xvfrVal; - - else if ( txvfVal != GempakConstants.RMISSD && xvfrVal != GempakConstants.RMISSD ) - prwxvf = txvfVal; - } - - return ( new Amount ( prwxvf , Unit.ONE ) ); - } - - - /** - *
-      * Computes LIFR/IFR/MVFR/VFR flight conditions based on ceiling and visibility.
-      * @param ceil - Ceiling in hundreds of feet
-      * @param vsby - Visibility in statute miles
-      * @return Flight conditions index value: 
-      *     0 - LIFR
-      *     1 - IFR
-      *     2 - MVFR
-      *     3 - VFR 
-      *  
- * @throws NullPointerException - * - */ - public static final Amount prXvfr ( Amount ceil, Amount vsby ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prXvfr:"); - //System.out.println(" ceil = " + ceil.doubleValue()); - //System.out.println(" vsby = " + vsby.doubleValue()); - - - double prxvfr = GempakConstants.RMISSD; - double vc = GempakConstants.RMISSD; - double vs = GempakConstants.RMISSD; - if ( vsby == null ) - return null; - - if ( !checkNullOrInvalidValue( ceil ) ) - return new Amount ( Unit.ONE ); - ceil = checkAndConvertInputAmountToExpectedUnits( ceil, NcUnits.HUNDREDS_OF_FEET ); - vsby = checkAndConvertInputAmountToExpectedUnits(vsby, NonSI.MILE ); - /*Compute categorical flight rules*/ - - //Check the ceiling value - - double ceilVal = ceil.doubleValue(); - - double vsbyVal = vsby.doubleValue(); - - if ( ceilVal < 0){ - //no-op. So vc retains its RMISSD value - } - else if ( ceilVal < 5 ) - vc = 0; - else if ( ceilVal < 10 ) - vc = 1; - else if ( ceilVal <= 30 ) - vc = 2; - else if ( ( vsbyVal > 5 ) - || ( vsbyVal < 0 ) - || ( vsbyVal == GempakConstants.RMISSD ) ){ - prxvfr = 3; - } - - /*Check the visibility value.*/ - if ( vsbyVal != GempakConstants.RMISSD ){ - if ( vsbyVal < 0 ){ - //no-op. So vs retains it RMISSD value - } - else if ( vsbyVal < 1 ) - vs = 0; - else if ( vsbyVal < 3) - vs = 1; - else if ( vsbyVal <= 5) - vs = 2; - else - vs = 3; - } - - /*Determine the more restrictive of the two values.*/ - if ( vc == GempakConstants.RMISSD ) - prxvfr = vs; - else if ( vs == GempakConstants.RMISSD ) - prxvfr = vc; - else - prxvfr = ( vc < vs ? vc : vs ); - - return ( new Amount ( prxvfr , Unit.ONE ) ) ; - - } - - /** - * Computes station elevation from altimeter and station pressure. - * It is also used to estimate height at various pressure levels from the altimeter in millibars. - * The PC library computes zmsl, Z000, Z950, Z850, Z800 by calling this function with pres - * equal to PMSL, 1000, 950, 850 and 800 respectively. - * @param altm - Altimeter in millibars - * @param pres - Pressure in millibars - * @return the height ( in meters ) if neither input value is missing and both input values - * are greater than zero. - * @throws NullPointerException - * - * @throws InvalidRangeException - */ - public static final Amount prZalt ( Amount altm, Amount pres ) throws InvalidValueException, NullPointerException{ - //System.out.println("From prZalt:"); - //System.out.println(" altm = " + altm.doubleValue()); - //System.out.println(" pres = " + pres.doubleValue()); - -// checkNullOrInvalidValue( pres ); -// checkNullOrInvalidValue( altm ); - - if ( !checkNullOrInvalidValue( pres ) - || !checkNullOrInvalidValue( altm ) ){ - return new Amount (SI.METER ); - } - - pres = checkAndConvertInputAmountToExpectedUnits( pres, NcUnits.MILLIBAR ); - altm = checkAndConvertInputAmountToExpectedUnits( altm, NcUnits.MILLIBAR ); - if ( altm.doubleValue() <= 0 ){ - // throw new InvalidRangeException("From prZalt: altm must be greater than 0 mb"); - System.out.println("From prZalt: altm must be greater than 0 mb"); - return new Amount (SI.METER ); - } - if ( pres.doubleValue() <= 0 ){ -// throw new InvalidRangeException("From prZalt: pres must be greater than 0 mb"); - System.out.println("From prZalt: pres must be greater than 0 mb"); - return new Amount (SI.METER ); - } - double to = GempakConstants.TMCK + 15; - double gamma = GempakConstants.GAMUSD / 1000 ; - - /*Calculate the exponent and pressure ratio.*/ - double expo = ( gamma * GempakConstants.RDGAS ) / GempakConstants.GRAVTY; - double prat = pres.doubleValue() / altm.doubleValue() ; - double przalt = ( to * ( 1 - Math.pow ( prat, expo) ) ) / gamma ; - - return ( new Amount ( przalt , SI.METER) ) ; - } - - - - /** - * Computes the windchill from the wind velocity ( part of the Falconer equation - refer method prWceq) - * @param d - wind velocity ( in meters per second ) - * @return the windchill temperature - */ - private static double wci ( double d){ - - /* from R. Falconer, "Windchill, A Useful Wintertime Weather Variable", Weatherwise, Dec 1968.*/ - return ( ( double ) (10 * Math.sqrt(d) + 10.45 - d )); - - - } - -public static final Amount checkAndConvertInputAmountToExpectedUnits(Amount amountIn, Unit expectedUnit){ - Amount amountOut = null; - if ( ! amountIn.getUnit().equals(expectedUnit) - && amountIn.getUnit().isCompatible(expectedUnit)){ - double newValue = amountIn.getUnit().getConverterTo(expectedUnit).convert(amountIn.doubleValue()); - amountOut = new Amount(newValue,expectedUnit); - }else -// throw new ConversionException("Unable to convert " + amountIn.getUnit().toString() + " to " + expectedUnit.toString() ); - amountOut = amountIn; - - return amountOut; - } - -// public static final void checkNullOrInvalidValue( Amount amountToCheck ) throws InvalidValueException, NullPointerException{ -// if (amountToCheck == null ) -// throw new NullPointerException(); -// else { -// double amountValue = amountToCheck.doubleValue(); -// if ( amountValue == GempakConstants.RMISSD){ -// throw new InvalidValueException( new String ("Input amount cannot be -9999")); -// } -// else if ( Double.isNaN(amountValue)) -// throw new InvalidValueException( new String ("Input amount cannot be NaN" )); -// } -// } - -public static final boolean checkNullOrInvalidValue( Amount amountToCheck ) { - if (amountToCheck == null ){ - return false; - // throw new NullPointerException(); - } - else { - double amountValue = amountToCheck.doubleValue(); - if ( amountValue == GempakConstants.RMISSD){ - System.out.println("Input amount cannot be -9999"); - return false; -// throw new InvalidValueException( new String ("Input amount cannot be -9999")); - } - else if ( Double.isNaN(amountValue)){ - System.out.println("Input amount cannot be NaN"); -// throw new InvalidValueException( new String ("Input amount cannot be NaN" )); - return false; - } - else - return true; } -} -// public static final class InvalidRangeException extends Exception { -// /** -// * -// */ -// private static final long serialVersionUID = -4962228676211688262L; -// -// /** -// * -// */ -// public InvalidRangeException( String msg) { -// super( msg ); -// } -// } - - public static final class InvalidValueException extends Exception { - /** + /** + * Computes the dewpoint as the difference between the input temperature and + * dewpoint depression + * + * @param tmpx + * - temperature (in Celsius or Farenheit or Kelvin) + * @param dpdx + * - the dewpoint depression ( in the same units as the + * temperature) + * @return the dewpoint in the same units as ( in the same units as the + * temperature) + * @throws NullPointerException + * + */ + public static final Amount prDwdp(Amount tmpx, Amount dpdx) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDwdp:"); + // System.out.println(" tmpx = " + tmpx.doubleValue()); + // System.out.println(" dpdx = " + dpdx.doubleValue()); + + // checkNullOrInvalidValue(tmpx); + // checkNullOrInvalidValue(dpdx); + + if (!checkNullOrInvalidValue(tmpx) || !checkNullOrInvalidValue(dpdx)) + return new Amount(tmpx.getUnit()); + + Unit tempUnits = (Unit) tmpx.getUnit(); + Unit dewpointDepUnits = (Unit) dpdx.getUnit(); + if (!tempUnits.equals(dewpointDepUnits) + && tempUnits.isCompatible(dewpointDepUnits)) { + double dewpointDepValue = (double) dewpointDepUnits.getConverterTo( + tempUnits).convert(dpdx.doubleValue()); + dpdx = new Amount(dewpointDepValue, tempUnits); + } + + Amount dewpointTemperature = new Amount(tmpx.doubleValue() + - dpdx.doubleValue(), tempUnits); + return dewpointTemperature; + } + + /** + * Computes the dewpoint ( in Celsius ) from the mixing ratio (in + * grams/kilogram) and the pressure (in mb) + * + * @param rmix + * - the mixing ratio (in grams/kilogram) + * @param pres + * - the pressure (in mb) + * @return the dewpoint (in Celsius), if both the input values are valid + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prDwpt(Amount rmix, Amount pres) + throws InvalidValueException, NullPointerException { + // System.out.println("From prDwpt:"); + // System.out.println(" rmix = " + rmix.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + Amount prDwpt = null; + // checkNullOrInvalidValue( rmix ); + // checkNullOrInvalidValue( pres ); + if (!checkNullOrInvalidValue(rmix) || !checkNullOrInvalidValue(pres)) + return new Amount(SI.CELSIUS); + + rmix = checkAndConvertInputAmountToExpectedUnits(rmix, Unit.ONE); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + double mixingRatioValue = rmix.doubleValue(); + double pressureValue = pres.doubleValue(); + if (mixingRatioValue <= 0) { + System.out + .println("From prDwpt() - mixing ratio must be greater than 0"); + return new Amount(SI.CELSIUS); + // throw new + // InvalidRangeException("From prDwpt() - mixing ratio must be greater than 0"); + } + if (pressureValue <= 0) { + System.out.println("From prDwpt() - pres must be greater than 0"); + return new Amount(SI.CELSIUS); + // throw new + // InvalidRangeException("From prDwpt() - pres must be greater than 0"); + } + /* Convert gram/kilogram to gram/gram */ + double ratio = mixingRatioValue / 1000; + + /* Calculate vapor pressure from mixing ratio and pressure */ + double vaporPressure = (pressureValue * ratio) / (0.62197 + ratio); + + /* Correct vapor pressure */ + vaporPressure = vaporPressure + / (1.001 + ((pressureValue - 100.0) / 900) * .0034); + + /* Calculate dewpoint */ + double dewPointValue = (double) (Math.log(vaporPressure / 6.112) * 243.5 / (17.67 - Math + .log(vaporPressure / 6.112))); + prDwpt = new Amount(dewPointValue, SI.CELSIUS); + return prDwpt; + } + + /*** + * Computes the Fosberg index from the temperature, relative humidity and + * wind speed at the surface. + * + * @param tmpc + * - Temperature in Celsius + * @param relh + * - Relative humidity in percent + * @param sped + * - Wind speed in meters/second + * @return the Fosberg index + * @throws NullPointerException + * + */ + public static final Amount prFosb(Amount tmpc, Amount relh, Amount sped) + throws InvalidValueException, NullPointerException { + // System.out.println("From prFosb:"); + // System.out.println("Temperature ( in K ) is: " + tmpc.doubleValue()); + // System.out.println("Relative Humidity is: " + relh.doubleValue()); + // System.out.println("Wind Speed is: " + sped.doubleValue()); + // checkNullOrInvalidValue( tmpc ); + // checkNullOrInvalidValue( relh ); + // checkNullOrInvalidValue( sped ); + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(relh) + || !checkNullOrInvalidValue(sped)) + return new Amount(Unit.ONE); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + relh = checkAndConvertInputAmountToExpectedUnits(relh, NonSI.PERCENT); + sped = checkAndConvertInputAmountToExpectedUnits(sped, + SI.METERS_PER_SECOND); + + /* Change temperature to degrees Fahrenheit */ + double tf = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT) + .convert(tmpc.doubleValue()); + + /* Convert wind speed from meters/second to knots */ + double smph = sped.getUnit().getConverterTo(NonSI.MILES_PER_HOUR) + .convert(sped.doubleValue()); + + double A = 0.03229; + double B = 0.281073; + double C = 0.000578; + double D = 2.22749; + double E = 0.160107; + double F = 0.014784; + double G = 21.0606; + double H = 0.005565; + double P = 0.00035; + double Q = 0.483199; + double R = 0.3002; + // float T = 9.0f/5.0f; + // float U = 1.9425f; + // float V = 0.868976f; + double fw = GempakConstants.RMISSD; + double relhVal = relh.doubleValue(); + if (relhVal <= 10) { + fw = A + B * relhVal - C * relhVal * tf; + } else if (relhVal <= 50) { + fw = D + E * relhVal - F * tf; + } else { + fw = G + H * relhVal * relhVal - P * relhVal * tf - Q * relhVal; + } + + double sss = (double) (Math.sqrt(1. + (smph * smph))); + double fwd = fw / 30; + double fwd2 = fwd * fwd; + double fwd3 = fwd2 * fwd; + double fire = 1 - 2 * fwd + 1.5f * fwd2 - 0.5f * fwd3; + + /* Find the Fosberg Index */ + + double prfosb = (fire * sss) / R; + + return (new Amount(prfosb, Unit.ONE)); + } + + /** + *
+     * Computes the Southern Region/CPC Rothfusz heat index.
+     * 
+     * The Rothfusz regression is optimal for TMPF > ~80 and RELH > ~40%.   
+     * This code applies a simple heat index formula and then resorts to   
+     * the Rothfusz regression only if the simple heat index exceeds 80,   
+     * implying temperatures near, but slightly below 80.  To make the    
+     * simple calculation continuous with the values obtained from the   
+     * Rothfusz regression, the simple result is averaged with TMPF in    
+     * computing the simple heat index value.                               
+     * Source:  NWS Southern Region SSD Technical Attachment SR 90-23  7/1/90.  
+     * Heat Index was originally known as the apparent temperature index 
+     * (Steadman, JAM, July, 1979).                                                                  
+     * This code includes adjustments made by the CPC for low RELH at high  
+     * TMPF and high RELH for TMPF in the mid 80's.
+     * 
+     * 
+     * @param tmpf  - the input air temperature
+     * @param relh   - the relative humidity 
+     * @return the heat index (in deg Farenheit) if both 
+     * the input air temperature and relative humidity
+     * are valid values
+     * 
+ * + * @throws NullPointerException + * + */ + public static final Amount prHeat(Amount tmpf, Amount relh) + throws InvalidValueException, NullPointerException { + // System.out.println("From prHeat:"); + // System.out.println(" tmpf = " + tmpf.doubleValue()); + // System.out.println(" relh = " + relh.doubleValue()); + + double prheat = GempakConstants.RMISSD; + // checkNullOrInvalidValue( tmpf ); + // checkNullOrInvalidValue( relh ); + if (!checkNullOrInvalidValue(tmpf) || !checkNullOrInvalidValue(relh)) + return new Amount(Unit.ONE); + + tmpf = checkAndConvertInputAmountToExpectedUnits(tmpf, NonSI.FAHRENHEIT); + relh = checkAndConvertInputAmountToExpectedUnits(relh, NonSI.PERCENT); + double tmpfVal = tmpf.doubleValue(); + double relhVal = relh.doubleValue(); + /* + * If the temperature is less than 40 degrees, set the heat index to the + * temperature + */ + if (tmpfVal <= 40) + prheat = tmpfVal; + else { + /* + * Compute a simple heat index. If the value is less than 80 deg F + * use it + */ + prheat = (float) (61 + (tmpfVal - 68) * 1.2 + relhVal * 0.094); + prheat = (float) ((tmpfVal + prheat) * 0.5); + /* Else compute the full regression value */ + if (prheat >= 80.0) { + double t2 = tmpfVal * tmpfVal; + double r2 = relhVal * relhVal; + prheat = (float) (-42.379 + 2.04901523 * tmpfVal + 10.14333127 + * relhVal - 0.22475541 * tmpfVal * relhVal - 0.00683783 + * t2 - 0.05481717 * r2 + 0.00122874 * t2 * relhVal + + 0.00085282 * tmpfVal * r2 - 0.00000199 * t2 * r2); + /* + * Adjust for high regression at low relative humidity for + * temperatures above 80 degrees F. + */ + if ((relhVal <= 13.0) + && ((tmpfVal >= 80.0) && (tmpfVal <= 112.0))) { + float adj1 = (float) ((13. - relhVal) / 4); + float adj2 = (float) (Math.sqrt((17 - Math + .abs(tmpfVal - 95)) / 17)); + float adj = adj1 * adj2; + prheat -= adj; + } + /* + * Adjust for low regression at high relative humidity and + * temperatures in the mid-80s + */ + else if ((relhVal > 85) + && ((tmpfVal >= 80.0) && (tmpfVal <= 87.0))) { + float adj1 = (float) ((relhVal - 85.0) / 10.0); + float adj2 = (float) ((87.0 - tmpfVal) / 5); + float adj = adj1 * adj2; + prheat += adj; + } + } + } + return (new Amount(prheat, NonSI.FAHRENHEIT)); + } + + /** + * Computes the humiture index from the air temperature and the dew point + * temperature using the equation: PR_HMTR = TMPF + ( PR_VAPR ( DWPC ) - 21 + * ) + * + * @param tmpf + * - the air temperature (in Farenheit) + * @param dwpf + * - the dew point (in Farenheit) + * @return the humiture index if both the air temperature and the dewpoint + * temperature are valid values + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prHmtr(Amount tmpf, Amount dwpf) + throws InvalidValueException, NullPointerException { + // System.out.println("From prHmtr:"); + // System.out.println(" tmpf = " + tmpf.doubleValue()); + // System.out.println(" dwpf = " + dwpf.doubleValue()); + + double prhmtr = GempakConstants.RMISSD; + if (!checkNullOrInvalidValue(tmpf) || !checkNullOrInvalidValue(dwpf)) + return new Amount(Unit.ONE); + + tmpf = checkAndConvertInputAmountToExpectedUnits(tmpf, NonSI.FAHRENHEIT); + dwpf = checkAndConvertInputAmountToExpectedUnits(dwpf, NonSI.FAHRENHEIT); + + Amount dwpc = checkAndConvertInputAmountToExpectedUnits(dwpf, + SI.CELSIUS); + Amount vapr = prVapr(dwpc); + if (!checkNullOrInvalidValue(vapr)) + return new Amount(Unit.ONE); + + prhmtr = tmpf.doubleValue() + (vapr.doubleValue() - 21); + + return (new Amount(prhmtr, Unit.ONE)); + } + + /** + * Computes the rate of ice accretion/growth of ice on a vessel in salt + * water, in units of inches per 3 hours (the WMO standard) The formula used + * is IGRO = ( A*pr + B*pr*pr + *pr*pr*pr ) * CVFAC where A = 2.73 * 10e-2 B + * = 2.91 * 10e-4 C = 1.84 * 10e-6 pr = ( sped * ( -1.7 - tmpc ) ) / ( 1 + + * 0.4 * ( sstc + 1.7 ) ) (priesendorfer regression) and CVFAC = 1.1811, to + * convert cm/hr to in/3hr. + * + * @param tmpc + * - the observed surface air temperature in Celsius + * @param sstc + * - the observed surface sea temperature in Celsius + * @param sped + * - the observed wind speed + * @return the rate of ice growth if all the input values are valid and lie + * between specific limits and if the rate of ice growth that is + * computed is greater than or equal to 0, + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prIgro(Amount tmpc, Amount sstc, Amount sped) + throws InvalidValueException, NullPointerException { + // System.out.println("From prIgro:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" sstc = " + sstc.doubleValue()); + // System.out.println(" sped = " + sped.doubleValue()); + + double prigro = GempakConstants.RMISSD; + // checkNullOrInvalidValue( tmpc ) ; + // checkNullOrInvalidValue( sstc ) ; + // checkNullOrInvalidValue( sped ) ; + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(sstc) + || !checkNullOrInvalidValue(sped)) + return new Amount(NcUnits.INCHES_PER_THREE_HOURS); + + checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + checkAndConvertInputAmountToExpectedUnits(sstc, SI.CELSIUS); + + // TODO: verify that wind speed can have any unit + double tmpcVal = tmpc.doubleValue(); + double sstcVal = sstc.doubleValue(); + double spedVal = sped.doubleValue(); + + /* Check that these values are within the valid range */ + + if (spedVal < 0 || spedVal > 50) { + // throw new + // InvalidRangeException("The wind speed must lie between 0 and 50. Both limits inclusive"); + System.out + .println("The wind speed must lie between 0 and 50. Both limits inclusive"); + return new Amount(NcUnits.INCHES_PER_THREE_HOURS); + } + if (tmpcVal < -20 || tmpcVal > 0) { + // throw new + // InvalidRangeException("The observed surface air temperature must lie between -20 and 0. Both limits inclusive"); + System.out + .println("The observed surface air temperature must lie between -20 and 0. Both limits inclusive"); + return new Amount(NcUnits.INCHES_PER_THREE_HOURS); + } + if (sstcVal < -1.7f || sstcVal > 12) { + // throw new + // InvalidRangeException("The observed surface sea temperature must lie between -1.7 and 12. Both limits inclusive"); + } + double A = 0.0273f; + double B = 0.000291f; + double C = 0.00000184f; + double cvfac = 1.1811f; // to convert cm/hr to in per 3 hours + double pr = ((spedVal * (-1.7 - tmpcVal)) / (1 + 0.4 * (sstcVal + 1.7))); // Compute + // the + // Priesendorfer + // regression + double pr2 = pr * pr; + prigro = (A * pr + B * pr2 + C * pr * pr2) * cvfac; + if (prigro < 0) { + // throw new + // InvalidRangeException("The rate of ice growth must be greater than or equal to 0"); + System.out + .println("The rate of ice growth must be greater than or equal to 0"); + return new Amount(NcUnits.INCHES_PER_THREE_HOURS); + } + return (new Amount(prigro, NcUnits.INCHES_PER_THREE_HOURS)); + } + + /** + * Computes the latent heat of vaporization at constant pressure from the + * input temperature (in Celsius) using the equation: LHVP = ( 2.500 - + * .00237 * TMPC ) * 10E6 LHVP is in J/kg. + * + * @param tmpc + * - the input temperature (in Celsius) + * @return the latent heat of vaporization at constant pressure if the input + * temperature is valid + * @throws NullPointerException + * + */ + public static final Amount prLhvp(Amount tmpc) + throws InvalidValueException, NullPointerException { + + // System.out.println("From prLhvp:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + + // checkNullOrInvalidValue(tmpc); + if (!checkNullOrInvalidValue(tmpc)) + return new Amount(NcUnits.JOULES_PER_KILOGRAM); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + double latentHeatOfVapr = (float) ((2.500 - 0.00237 * tmpc + .doubleValue()) * 1000000); + return (new Amount(latentHeatOfVapr, NcUnits.JOULES_PER_KILOGRAM)); + } + + /** + * Computes the temperature of a parcel lifted (or sunk) adiabatically to a + * given pressure. + * + * @param thta + * - Potential temperature in Kelvin + * @param thte + * - Equivalent potential temp in Kelvin + * @param pres + * - Lifted pressure in millibar + * @return the lifted temperature in Celsius, if all the input parameters + * are valid + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prLtmp(Amount thta, Amount thte, Amount pres) + throws InvalidValueException, NullPointerException { + // System.out.println("From prLtmp:"); + // System.out.println(" thta = " + thta.doubleValue()); + // System.out.println(" thte = " + thte.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + + double prltmp = GempakConstants.RMISSD; + // checkNullOrInvalidValue(thta); + // checkNullOrInvalidValue(thte); + // checkNullOrInvalidValue(pres); + + if (!checkNullOrInvalidValue(thta) || !checkNullOrInvalidValue(thte) + || !checkNullOrInvalidValue(pres)) + return new Amount(SI.CELSIUS); + + thta = checkAndConvertInputAmountToExpectedUnits(thta, SI.KELVIN); + thte = checkAndConvertInputAmountToExpectedUnits(thte, SI.KELVIN); + + if (pres.doubleValue() <= 0) + pres = new Amount(500, NcUnits.MILLIBAR); + + /* Compute parcel temperatures on moist and dry adiabats */ + Amount tmpe = prTmst(thte, pres, new Amount(0, SI.KELVIN)); + Amount tmpd = prTmpk(pres, thta); + checkNullOrInvalidValue(tmpe); + checkNullOrInvalidValue(tmpd); + /* + * ( Non-Javadoc ) The correct parcel temperature is the warmer of the + * temperature on the dry adiabat and the temperature on the moist + * adiabat. + */ + + double tmpeVal = tmpe.doubleValue(); + double tmpdVal = tmpd.doubleValue(); + if (tmpeVal > tmpdVal) { + prltmp = SI.KELVIN.getConverterTo(SI.CELSIUS).convert(tmpeVal); + } else { + prltmp = SI.KELVIN.getConverterTo(SI.CELSIUS).convert(tmpdVal); + } + + return (new Amount(prltmp, SI.CELSIUS)); + } + + /** + * Computes the mountain obscuration threshold met indicator + * + * @param cmsl + * - Ceiling converted to MSL in 100's of ft + * @param otval + * - Mountain obscuration threshold in 100's of ft + * @return The mountain obscuration threshold met indicator if the input + * values are valid + * @throws NullPointerException + * + */ + public static final Amount prMobs(Amount cmsl, Amount otval) + throws InvalidValueException, NullPointerException { + // System.out.println("From prMobs:"); + // System.out.println(" cmsl = " + cmsl.doubleValue()); + // System.out.println(" otval = " + otval.doubleValue()); + + // checkNullOrInvalidValue( cmsl ); + // checkNullOrInvalidValue( otval ); + + if (!checkNullOrInvalidValue(cmsl) || !checkNullOrInvalidValue(otval)) + return new Amount(Unit.ONE); + + cmsl = checkAndConvertInputAmountToExpectedUnits(cmsl, + NcUnits.HUNDREDS_OF_FEET); + otval = checkAndConvertInputAmountToExpectedUnits(otval, + NcUnits.HUNDREDS_OF_FEET); + return (cmsl.doubleValue() < otval.doubleValue() ? new Amount(1, + Unit.ONE) : new Amount(1, Unit.ONE)); + } + + /** + * Computes the mixing ratio in grams/kilograms from the dewpoint ( in + * Celsius ) and the pressure ( in mb) using the equation: MIXR = .62197 * ( + * e / ( PRES - e ) ) * 1000. where e = VAPR * corr corr = (1.001 + ( ( PRES + * - 100. ) / 900. ) * .0034) ( University of Wisconsin green sheet ). This + * method can also be used for the folloiwng computations: MIXS from TMPC + * and PRES SMXR from DWPC and PALT SMXS from TMPC and PALT + * + * @param dwpc + * - the dewpoint ( in Celsius ) + * @param pres + * - the pressure ( in mb) + * @return the missing ratio ( in grams / kilograms ) if both the input + * parameters are valid + * @throws NullPointerException + * + */ + public static final Amount prMixr(Amount dwpc, Amount pres) { + // System.out.println("From prMixr:"); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + + Amount prmixr = new Amount(-9999.0, Unit.ONE); + // checkNullOrInvalidValue(pres); + // checkNullOrInvalidValue(dwpc); + + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(dwpc)) + return new Amount(NcUnits.GRAMS_PER_KILOGRAM); + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); + + /* Calculate vapor pressure */ + Amount vapr = prVapr(dwpc); + + if (!checkNullOrInvalidValue(vapr)) + return new Amount(NcUnits.GRAMS_PER_KILOGRAM); + + vapr = checkAndConvertInputAmountToExpectedUnits(vapr, NcUnits.MILLIBAR); + double pressureValue = pres.doubleValue(); + double vaporPressureValue = vapr.doubleValue(); + /* + * (Non-Javadoc) corr is a correction to the vapor pressure since the + * atmosphere is not an ideal gas. + */ + double corr = (double) (1.001 + ((pressureValue - 100) / 900) * 0.0034); + double e = corr * vaporPressureValue; + + /* + * Test for unphysical case of large E at low PRES + */ + if (e <= (0.5 * pressureValue)) { + /* Calculate mixing ratio */ + prmixr = new Amount( + (double) (0.62197 * (e / (pressureValue - e)) * 1000), + NcUnits.GRAMS_PER_KILOGRAM); + } + return prmixr; + } + + /** + * Extracts the pressure change ( in millibars ) from the pressure tendency + * information + * + * @param p03d + * - Pressure tendency information + * @return the pressure change ( in mb ) + * @throws NullPointerException + * + */ + // TODO : remove it to make it a part of display options or let it stay? + public static final Amount prP03c(Amount p03d) + throws InvalidValueException, NullPointerException { + // System.out.println("From prP03c:"); + // System.out.println(" p03d = " + p03d.doubleValue()); + + double prp03c = GempakConstants.RMISSD; + // checkNullOrInvalidValue( p03d ); + if (!checkNullOrInvalidValue(p03d)) + return new Amount(NcUnits.MILLIBAR); + + double p03dVal = p03d.doubleValue(); + float[] psign = { 1, 1, 1, 1, 0, -1, -1, -1, -1 }; + int itendc = (int) (p03dVal / 1000); + float ptend = (float) (((int) p03dVal) % 1000) / 10f; + // TODO: compare tests with legacy + if (itendc < psign.length) + prp03c = psign[itendc] * ptend; + + return (new Amount(prp03c, NcUnits.MILLIBAR)); + } + + /** + * Computes station pressure from altimeter and station elevation using the + * equation PALT = ALTM * ( 1 - ( SELK * GAMUSD / To ) ) ** expo where SELK + * = SELV / 1000 To = US Std. Atmos. sea level temp in Kelvin = TMCK + 15 + * expo = GRAVTY / ( GAMUSD * RDGAS ) * 1000 Wallace and Hobbs. + * + * @param altm + * - Altimeter in millibars + * @param selv + * - Station elevation in meters + * @return the pressure in millibars if none of the input values are missing + * @throws NullPointerException + * + */ + public static final Amount prPalt(Amount altm, Amount selv) + throws InvalidValueException, NullPointerException { + // System.out.println("From prPalt:"); + // System.out.println(" altm = " + altm.doubleValue()); + // System.out.println(" selv = " + selv.doubleValue()); + + // checkNullOrInvalidValue( altm ); + // checkNullOrInvalidValue( selv ); + + if (!checkNullOrInvalidValue(altm) || !checkNullOrInvalidValue(selv)) + return new Amount(NcUnits.MILLIBAR); + + altm = checkAndConvertInputAmountToExpectedUnits(altm, NcUnits.MILLIBAR); + selv = checkAndConvertInputAmountToExpectedUnits(selv, SI.METER); + double hgtk = selv.getUnit().getConverterTo(SI.KILOMETER) + .convert(selv.doubleValue()); + + /* Calculate the exponent */ + double expo = (GempakConstants.GRAVTY + / (GempakConstants.GAMUSD * GempakConstants.RDGAS) * 1000.0f); + + /* Calculate pressure */ + double prpalt = (altm.doubleValue() * Math.pow((1 - (hgtk + * GempakConstants.GAMUSD / (GempakConstants.TMCK + 15))), expo)); + + return (new Amount(prpalt, NcUnits.MILLIBAR)); + } + + /** + * Computes the lifted condensation level pressure ( in mb ) for a parcel of + * air from TMPC, PRES, and TLCL. TLCL may be computed using PR_TLCL. The + * equation used is a modified Poisson equation: PLCL = PRES * ( TLCL / TMPK + * ) ** ( 1 / RKAPPA ) + * + * @param tmpc + * - Temperature ( in Celsius ) before lifting the air parcel + * @param pres + * - Pressure ( in mb ) before lifting the air parcel + * @param tlcl + * - Temperature ( in Kelvin ) at the lifted condensation level + * @return the pressure at the lifted condensation level, if all the inputs + * are valid + * @throws NullPointerException + * + */ + public static final Amount prPlcl(Amount tmpc, Amount pres, Amount tlcl) + throws InvalidValueException, NullPointerException { + double prplcl = GempakConstants.RMISSD; + // System.out.println("From prPlcl:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println(" tlcl = " + tlcl.doubleValue()); + + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(pres); + // checkNullOrInvalidValue(tlcl); + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(pres) + || !checkNullOrInvalidValue(tlcl)) + return new Amount(NcUnits.MILLIBAR); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + tlcl = checkAndConvertInputAmountToExpectedUnits(tlcl, SI.KELVIN); + Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); + double tclValue = tlcl.doubleValue(); + double tmpkValue = tmpk.doubleValue(); + double presValue = pres.doubleValue(); + prplcl = (double) (presValue * Math.pow((tclValue / tmpkValue), + (1 / GempakConstants.RKAPPA))); + return new Amount(prplcl, NcUnits.MILLIBAR); + } + + /** + *
+     *  Computes the mean sea level pressure ( in mb ) from the station pressure ( in mb ),
+     *  the temperature ( in deg Celsius), the dewpoint ( in deg Celsius ) and 
+     *  the station elevation ( in meters ) using the equation:
+     *  	PMSL = PRES * EXP ( ( GRAVTY * SELV ) / ( RDGAS * TVAVE ) ) 
+     *     where
+     *     		 TVAVE = avg virtual temp between station and sea level 
+     *     		        = TVRK + ( DELTV / 2 )
+     *     		 DELTV = GAMUSD * SELV / 1000
+     *  Wallace and Hobbs.
+     * @param pres - the station pressure ( in mb )
+     * @param tmpc - the temperature ( in deg Celsius)
+     * @param dwpc - the dewpoint ( in deg Celsius )
+     * @param selv - the station elevation ( in meters )
+     * @return the mean sea level pressure ( in mb ) if all the inputs are valid
+     * 
+ * + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prPmsl(Amount pres, Amount tmpc, Amount dwpc, + Amount selv) throws InvalidValueException, NullPointerException { + // System.out.println("From prPmsl:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + // System.out.println(" selv = " + selv.doubleValue()); + // checkNullOrInvalidValue( pres ); + // checkNullOrInvalidValue( tmpc ); + // checkNullOrInvalidValue( dwpc ); + // checkNullOrInvalidValue( selv ); + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(pres) + || !checkNullOrInvalidValue(dwpc) + || !checkNullOrInvalidValue(selv)) + return new Amount(NcUnits.MILLIBAR); + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + tmpc = checkAndConvertInputAmountToExpectedUnits(pres, SI.CELSIUS); + dwpc = checkAndConvertInputAmountToExpectedUnits(pres, SI.CELSIUS); + selv = checkAndConvertInputAmountToExpectedUnits(pres, SI.METER); + + /* Calculate virtual temperature */ + Amount tv = prTvrk(tmpc, dwpc, pres); + + /* deltaV and tVave */ + double selvVal = selv.doubleValue(); + double deltaV = selvVal * GempakConstants.GAMUSD / 1000; + double tVave = tv.doubleValue() + (deltaV / 2); + double mathFormula = (GempakConstants.GRAVTY * selvVal) + / (GempakConstants.RDGAS * tVave); + double prpmsl = (pres.doubleValue() * Math.exp(mathFormula)); + + return (new Amount(prpmsl, NcUnits.MILLIBAR)); + } + + /** + * Computes the maximum precipitation amount for upto 4 preciptiation values + * in inches + * + * @param p01 + * - First precipitation amount + * @param p02 + * - Second precipitation amount + * @param p03 + * - Third precipitation amount + * @param p04 + * - Fourth precipitation amount + * @return the maximum precipitation + * @throws NullPointerException + * + */ + + public static final Amount prPr6x(Amount p01, Amount p02, Amount p03, + Amount p04) throws InvalidValueException, NullPointerException { + // System.out.println("From prPr6x:"); + // System.out.println(" p01 = " + p01.doubleValue()); + // System.out.println(" p02 = " + p02.doubleValue()); + // System.out.println(" p03 = " + p03.doubleValue()); + // System.out.println(" p04 = " + p04.doubleValue()); + Amount[] tempArray = { p01, p02, p03, p04 }; + int index = 0; + double[] tempDblArray = new double[4]; + for (Amount thisAmount : tempArray) { + if (!checkNullOrInvalidValue(thisAmount)) { + return new Amount(NonSI.INCH); + } + + if (thisAmount.getUnit() != NonSI.INCH) { + thisAmount = checkAndConvertInputAmountToExpectedUnits( + thisAmount, NonSI.INCH); + tempArray[index] = thisAmount; + } + tempDblArray[index] = thisAmount.doubleValue(); + index++; + } + + Arrays.sort(tempDblArray); + return (new Amount(tempDblArray[3], NonSI.INCH)); + } + + /** + * Computes PR24, the 24-hour precipitation calculated by summing four + * 6-hour precipitation values + * + * @param p01 + * - First 6-hour precipitation amount + * @param p02 + * - Second 6-hour precipitation amount + * @param p03 + * - Third 6-hour precipitation amount + * @param p04 + * - Fourth 6-hour precipitation amount + * @return the total 24-hour precipitation amount + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prPr24(Amount p01, Amount p02, Amount p03, + Amount p04) throws InvalidValueException, NullPointerException { + // System.out.println("From prPr24:"); + // System.out.println(" p01 = " + p01.doubleValue()); + // System.out.println(" p02 = " + p02.doubleValue()); + // System.out.println(" p03 = " + p03.doubleValue()); + // System.out.println(" p04 = " + p04.doubleValue()); + + // checkNullOrInvalidValue( p01 ); + // checkNullOrInvalidValue( p02 ); + // checkNullOrInvalidValue( p03 ); + // checkNullOrInvalidValue( p04 ); + + if (!checkNullOrInvalidValue(p01) || !checkNullOrInvalidValue(p02) + || !checkNullOrInvalidValue(p03) + || !checkNullOrInvalidValue(p04)) + return new Amount(NonSI.INCH); + + Amount[] tempArray = { p01, p02, p03, p04 }; + Arrays.sort(tempArray); + + Amount p24 = tempArray[3]; + double p01Val = p01.doubleValue(); + double p02Val = p02.doubleValue(); + double p03Val = p03.doubleValue(); + double p04Val = p04.doubleValue(); + double p24Val = p24.doubleValue(); + + if (p24Val > 0) { + p24Val = 0; + if (p01Val > 0) + p24Val += p01Val; + + if (p02Val > 0) + p24Val += p01Val; + + if (p03Val > 0) + p24Val += p01Val; + + if (p04Val > 0) + p24Val += p01Val; + + } + + if (p24Val < 0) { + // throw new + // InvalidRangeException("From prPr24: the total 24 hour precipitation amount cannot be less than 0 inches"); + System.out + .println("From prPr24: the total 24 hour precipitation amount cannot be less than 0 inches"); + return new Amount(NonSI.INCH); + } + return (new Amount(p24Val, NonSI.INCH)); + } + + /** + * Computes the station pressure ( in mb ) from the temperature ( in deg + * Celsius ) and the potential temperature ( in Kelvin ) using Poisson's + * equation: PRES = 1000. * ( PR_TMCK (TMPC) / THTA ) ** (1 / RKAPPA) + * + * @param tmpc + * - temperature (in deg Celsius) + * @param thta + * - potential temperature ( in Kelvin ) + * @return the station pressure ( in mb ) if both the inputs are valid + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prPres(Amount tmpc, Amount thta) + throws InvalidValueException, NullPointerException { + // System.out.println("From prPres:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" thta = " + thta.doubleValue()); + + // checkNullOrInvalidValue( tmpc ); + // checkNullOrInvalidValue( thta ); + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(thta)) + return new Amount(NcUnits.MILLIBAR); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + thta = checkAndConvertInputAmountToExpectedUnits(thta, SI.KELVIN); + double tmpcVal = tmpc.doubleValue(); + double thtaVal = thta.doubleValue(); + if (tmpcVal <= -GempakConstants.TMCK) { + // throw new + // InvalidRangeException("From prPres: the temperature must be greater than -273.15"); + System.out + .println("From prPres: the temperature must be greater than -273.15"); + return new Amount(NcUnits.MILLIBAR); + } + if (thtaVal <= 0) { + // throw new + // InvalidRangeException("From prPres: the potential temperature must be greater than 0"); + System.out + .println("From prPres: the potential temperature must be greater than 0"); + return new Amount(NcUnits.MILLIBAR); + } + double tmpkVal = tmpc.getUnit().getConverterTo(SI.KELVIN) + .convert(tmpcVal); + double prpres = (float) (1000 * Math.pow(tmpkVal / thtaVal, + 1 / GempakConstants.RKAPPA)); + + return (new Amount(prpres, NcUnits.MILLIBAR)); + } + + /** + * Extracts the symbol code from the pressure tendency information. The code + * number is returned follow by 999 so that the output is a 4-digit number. + * + * @param p03d + * - the pressure tendency information + * @return the pressure tendency symbol code if the input is valid + * @throws NullPointerException + * + */ + // TODO add it to the Met Parameters or remove it and make it part of the + // display options instead? + public static final Amount prPtsy(Amount p03d) + throws InvalidValueException, NullPointerException { + // System.out.println("From prPtsy:"); + // System.out.println(" p03d = " + p03d.doubleValue()); + + if (!checkNullOrInvalidValue(p03d)) + return new Amount(Unit.ONE); + + double p03dVal = p03d.doubleValue(); + int prptsy = -9999; + if (!(p03dVal < 0) & !(p03dVal >= 9000)) { + prptsy = ((int) (p03dVal / 1000)) * 1000 + 999; + } + return (new Amount(prptsy, Unit.ONE)); + } + + /** + * Computes the relative humidity ( in percent ) from the input temperature + * and dewpoint using the equation: RELH = VAPR / VAPS * 100 where VAPR = + * vapor pressure = PR_VAPR ( DWPC ) VAPS = saturation vapor pressure = + * PR_VAPR ( TMPC ) + * + * @param tmpc + * - temperature ( in Celsius ) + * @param dwpc + * - dewpoint ( in Celsius) + * @return the relative humidity ( in percent ) if both inputs are valid and + * RMISSD ( -9999.0 ) otherwise + * @throws NullPointerException + * + */ + public static final Amount prRelh(Amount tmpc, Amount dwpc) + throws InvalidValueException, NullPointerException { + // System.out.println("From prRelh:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + + double prrelh = GempakConstants.RMISSD; + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(dwpc); + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(dwpc)) + return new Amount(NonSI.PERCENT); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); + + /* Find the vapor pressure */ + Amount e = prVapr(dwpc); + + if (!checkNullOrInvalidValue(e)) + return new Amount(NonSI.PERCENT); + + /* Find the saturated vapor pressure */ + Amount es = prVapr(tmpc); + + if (!checkNullOrInvalidValue(es)) + return new Amount(NonSI.PERCENT); + + /* Calculate humidity */ + prrelh = (e.doubleValue() / es.doubleValue()) * 100; + + return new Amount(prrelh, NonSI.PERCENT); + } + + /** + * Computes the dewpoint (in Celsius) from the temperature ( in Celsius ) + * and the relative humidity ( in percent ). + * + * @param tmpc + * - the temperature ( in deg Celsius ) + * @param relh + * - the relative humidity ( in percent ) + * @return the dewpoint in ( deg Celsius), if both inputs are valid and the + * value of the vapor pressure computed is greater than ( 1* + * e^(-30)) + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prRhdp(Amount tmpc, Amount relh) + throws InvalidValueException, NullPointerException { + // System.out.println("From prRhdp:"); + // System.out.println(" tmpc =" + tmpc.doubleValue()); + // System.out.println(" relh = " + relh.doubleValue()); + + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(relh); + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(relh)) + return new Amount(SI.CELSIUS); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + relh = checkAndConvertInputAmountToExpectedUnits(relh, NonSI.PERCENT); + + /* Calculate saturation vapor pressure; test for existence */ + Amount vaps = prVapr(tmpc); + + if (!checkNullOrInvalidValue(vaps)) + return new Amount(SI.CELSIUS); + + /* Calculate vapor pressure */ + double relativeHumidity = relh.doubleValue(); + double saturationVaporPressure = vaps.doubleValue(); + double vapr = relativeHumidity * saturationVaporPressure / 100; + + /* Calculate dewpoint. The VAPR test prevents LOG blowups */ + double prrhdp = -191; + Amount dewpointAmount = null; + if (vapr >= (Math.pow(Math.E, -30))) {// legacy checks for 1.E-30 + prrhdp = (double) (243.5 * (Math.log(6.112) - Math.log(vapr)) / (Math + .log(vapr) - Math.log(6.112) - 17.67)); + + /* + * If the dew-point is less than -190 degrees C, it is treated as + * missing data Note: Legacy documents it but does not implement it. + * However, in CAVE, it was decided to implement it. + */ + + if (prrhdp < -190) { + System.out + .println(" From prRhdp: dewpoint is less than -190 C"); + return new Amount(SI.CELSIUS); + } + } + dewpointAmount = new Amount(prrhdp, SI.CELSIUS); + return dewpointAmount; + } + + public static class RZLL // implements ISerializableObject + { + // @DynamicSerializeElement + private static RZLL rzll; + + /** Station latitude in degrees */ + // @DynamicSerializeElement + Amount stltdg = null; + + /** Station longitude in degrees */ + // @DynamicSerializeElement + Amount stlndg = null; + + /** Range in kilometers */ + // @DynamicSerializeElement + Amount range = null; + + /** Geographic azimuth in radians */ + // @DynamicSerializeElement + Amount azim = null; + + /** Height above the ground in kilometers */ + // @DynamicSerializeElement + Amount hght = null; + + /** Latitude in degrees */ + // @DynamicSerializeElement + Amount xlat = null; + + /** Longitude in degrees */ + // @DynamicSerializeElement + Amount xlon = null; + + /** + * @return the xlat + */ + public Amount getXlat() { + return xlat; + } + + /** + * @return the xlon + */ + public Amount getXlon() { + return xlon; + } + + private static RZLL getInstance() { + if (rzll == null) { + rzll = new RZLL(); + } + return rzll; + } + + /** + * Computes the actual latitude/longitude given the station + * latitude/longitude, elevation and azimuth. It uses equations + * developed for use in the AOIPS radar. + * + * @param instltdg + * - Station latitude in degrees + * @param instlndg + * - Station longitude in degrees + * @param inrange + * - Range in kilometers + * @param inazim + * - Geographic azimuth in radians + * @param inhght + * - Height above ground in km + * @throws NullPointerException + * + */ + public void prRzll(Amount instltdg, Amount instlndg, Amount inrange, + Amount inazim, Amount inhght) throws InvalidValueException, + NullPointerException { + // System.out.println("From prRzll:"); + // System.out.println(" instltdg = " + instltdg.doubleValue()); + // System.out.println(" instlndg = " + instlndg.doubleValue()); + // System.out.println(" inrange = " + inrange.doubleValue()); + // System.out.println(" inazim = " + inazim.doubleValue()); + // System.out.println(" inhght = " + inhght.doubleValue()); + + // checkNullOrInvalidValue( instltdg ); + // checkNullOrInvalidValue( instlndg ); + // checkNullOrInvalidValue(inrange); + // checkNullOrInvalidValue(inazim); + // checkNullOrInvalidValue(inhght); + + if (!checkNullOrInvalidValue(instltdg) + || !checkNullOrInvalidValue(instlndg) + || !checkNullOrInvalidValue(inrange) + || !checkNullOrInvalidValue(inazim) + || !checkNullOrInvalidValue(inhght)) + return; + + instltdg = checkAndConvertInputAmountToExpectedUnits(instltdg, + NonSI.DEGREE_ANGLE); + instlndg = checkAndConvertInputAmountToExpectedUnits(instlndg, + NonSI.DEGREE_ANGLE); + inrange = checkAndConvertInputAmountToExpectedUnits(inrange, + SI.KILOMETER); + inazim = checkAndConvertInputAmountToExpectedUnits(inazim, + SI.RADIAN); + inhght = checkAndConvertInputAmountToExpectedUnits(inhght, + SI.KILOMETER); + + this.stltdg = new Amount(instltdg.doubleValue(), NonSI.DEGREE_ANGLE); + this.stlndg = new Amount(instlndg.doubleValue(), NonSI.DEGREE_ANGLE); + this.range = new Amount(inrange.doubleValue(), SI.KILOMETER); + this.azim = new Amount(inazim.doubleValue(), SI.RADIAN); + this.hght = new Amount(inhght.doubleValue(), SI.KILOMETER); + + double hdr = GempakConstants.RMISSD; + double elev = GempakConstants.RMISSD; + double rad = GempakConstants.RMISSD; + double radp = GempakConstants.RMISSD; + + /* Convert the station lat/lon to radians */ + Amount stlat = checkAndConvertInputAmountToExpectedUnits(stltdg, + NonSI.DEGREE_ANGLE); + Amount stlon = checkAndConvertInputAmountToExpectedUnits(stlndg, + NonSI.DEGREE_ANGLE); + + /* Get the elevation angle */ + hdr = (range.doubleValue() == 0.0f ? 0.0f : hght.doubleValue() + / range.doubleValue()); + // elev = (float) ( Math.abs(hdr) < 1.0f ? Math.asin(hdr) : 0.0f ); + elev = (Math.abs(hdr) <= 1.0f ? Math.asin(hdr) : 0.0f); + + double temp = (Math.pow(Math.sin(stlat.doubleValue()), 2)); + + /* Get the earth's corrected radius */ + rad = (6378.4 / Math.sqrt(1 + (0.00677 * temp))); + radp = 4 * (rad / 3); + + double dist = GempakConstants.RMISSD; + double cx = GempakConstants.RMISSD; + double cy = GempakConstants.RMISSD; + double mathFormula1 = GempakConstants.RMISSD; + double mathFormula2 = GempakConstants.RMISSD; + + /* Calculate the distance */ + double rangeVal = range.doubleValue(); + if (elev > 0.2618f) + dist = (double) (rangeVal * Math.cos(elev)); + else { + mathFormula1 = (double) ((1 - (Math.pow(elev, 2) / 2)) - rangeVal + * elev / radp); + dist = rangeVal * mathFormula1; + } + + /* Calculate the latitude and longitude */ + double azimVal = azim.doubleValue(); + cx = (double) (dist * Math.sin(azimVal)); + cy = (double) (dist * Math.cos(azimVal)); + + // mathFormula2 = ( float ) ( ( ( 2 * Math.pow( rad, 2 ) ) * + // Math.tan( stlat ) )); + // xlat = ( float ) ( stlat + ( cy / rad ) - ( Math.pow(cx, 2) / + // mathFormula2 ) ); + double stlatVal = stlat.doubleValue(); + mathFormula2 = (double) ((Math.pow(cx, 2) / (2 * Math.pow(rad, 2)) * Math + .tan(stlatVal))); + double xlatVal = (double) (stlatVal + (cy / rad) - mathFormula2); + double xlonVal = (double) (stlon.doubleValue() + (cx / (rad * Math + .cos(xlat.doubleValue())))); + + /* Change lat/lon to degrees */ + xlatVal = SI.RADIAN.getConverterTo(NonSI.DEGREE_ANGLE).convert( + xlatVal); + xlonVal = SI.RADIAN.getConverterTo(NonSI.DEGREE_ANGLE).convert( + xlonVal); + + this.xlat = new Amount(xlatVal, NonSI.DEGREE_ANGLE); + this.xlon = new Amount(xlonVal, NonSI.DEGREE_ANGLE); + } + } + + /** + * Computes the wind speed from the 'U' and 'V' components of the wind + * velocity. The formula is the square root of ( u^2 + v^2 ) + * + * @param uWnd + * - U component of velocity + * @param vWnd + * - V component of velocity + * @return the computed windspeed if both inputs are valid + * @throws NullPointerException + * + */ + public static final Amount prSped(Amount uWnd, Amount vWnd) + throws InvalidValueException, NullPointerException { + // System.out.println("From prSped:"); + // System.out.println(" uWnd = " +uWnd.doubleValue()); + // System.out.println(" vWnd = " + vWnd.doubleValue()); + + // checkNullOrInvalidValue( uWnd ); + // checkNullOrInvalidValue( vWnd ); + if (!checkNullOrInvalidValue(uWnd) || !checkNullOrInvalidValue(vWnd)) + return new Amount(uWnd.getUnit()); + + Unit uWndUnits = uWnd.getUnit(); + Unit vWndUnits = vWnd.getUnit(); + if (uWndUnits != vWndUnits && uWndUnits.isCompatible(vWndUnits)) { + double vWndVal = vWndUnits.getConverterTo(uWndUnits).convert( + vWnd.doubleValue()); + vWnd = new Amount(vWndVal, uWndUnits); + } + double prsped = (Math.sqrt((Math.pow(uWnd.doubleValue(), 2) + Math.pow( + vWnd.doubleValue(), 2)))); + return new Amount(prsped, uWndUnits); + } + + /** + * Computes the potential temperature ( in Kelvin ) from the temperature (in + * Celsius ) and the pressure ( in mb ). + * + * @param tmpc + * - The temperature ( in Celsius ) + * @param pres + * - The pressure ( in mb ) + * @return the potential temperature ( in Kelvin ), if both inputs are valid + * . + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static Amount prThta(Amount tmpc, Amount pres) + throws InvalidValueException, NullPointerException { + checkNullOrInvalidValue(tmpc); + checkNullOrInvalidValue(pres); + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(pres)) + return new Amount(SI.KELVIN); + + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + double pressureValue = pres.doubleValue(); + if (pressureValue <= 0) { + System.out.println("From prThta( ) - pressure must be > 0 "); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException("From prThta( ) - pressure must be > 0 "); + } + + /* Change temperature in degrees Celsius to Kelvin. */ + double temperatureInKelvin = (double) tmpc.getUnit() + .getConverterTo(SI.KELVIN).convert(tmpc.doubleValue()); + + /* Calculate theta using Poisson's equation */ + double prthta = (double) (temperatureInKelvin * Math.pow( + (1000 / pres.doubleValue()), GempakConstants.RKAPPA)); + return new Amount(prthta, SI.KELVIN); + } + + /** + * Computes the equivalent potential temperature ( in Kelvin ) from the + * pressure ( in mb ), the temperature ( in Celsius ) and the dewpoint ( in + * Celsius ) using the equation: THTE = THTAM * EXP [ ( 3.376/TLCL - .00254 + * ) * ( MIXR * ( 1 + .81*.001*MIXR ) ) ] where THTAM = potential + * temperature of moist air = TMPK * (1000 / PRES) ** E E = RKAPPA * ( 1 - ( + * .28 * .001 * MIXR ) ) Bolton. + * + * @param pres + * - the pressure ( in mb ) + * @param tmpc + * - the temperature ( in Celsius ) + * @param dwpc + * - the dewpoint ( in Celsius ) + * @return the the equivalent potential temperature ( in Kelvin ), if all + * the input values are valid + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prThte(Amount pres, Amount tmpc, Amount dwpc) { + // System.out.println("From prThte:"); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + + // checkNullOrInvalidValue(pres); + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(dwpc); + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(pres) + || !checkNullOrInvalidValue(dwpc)) + return new Amount(SI.KELVIN); + + if (pres.doubleValue() <= 0) { + System.out + .println("From prThte() - Input pressure must be greater than 0 "); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException("From prThte() - Input pressure must be greater than 0 "); + } + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + + /* Find mixing ratio */ + Amount rmix = prMixr(dwpc, pres); + if (!checkNullOrInvalidValue(rmix)) + return new Amount(SI.KELVIN); + /* Change degrees Celsius to Kelvin */ + Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); + + /* Calculate theta for moist air (thtam) */ + double mixingRatioVal = rmix.doubleValue(); + double pressureVal = pres.doubleValue(); + double tempVal = tmpk.doubleValue(); + + double e = (double) (GempakConstants.RKAPPA * (1 - (0.28 * 0.001 * mixingRatioVal))); + double thtam = (double) (tempVal * Math.pow(1000 / pressureVal, e)); + + /* Find the temperature at the lifted condensation level */ + Amount tlcl = prTlcl(tmpc, dwpc); + + if (!checkNullOrInvalidValue(tlcl)) + return new Amount(SI.KELVIN); + + double lclTemp = tlcl.doubleValue(); + e = ((3.376f / lclTemp) - 0.00254f) + * (mixingRatioVal * (1 + 0.81f * 0.001f * mixingRatioVal)); + double prthte = (double) (thtam * Math.exp(e)); + Amount equivPotentialTempAmount = new Amount(prthte, SI.KELVIN); + return equivPotentialTempAmount; + } + + /** + * Computes wet bulb potential temperature ( in Celsius ) from the pressure, + * temperature and dewpoint. The result is obtained by first computing the + * equivalent potential temperature (thte) of the the air parcel at level + * pres. Then the air parcel is brought to 1000 mb moist adiabatically to + * get the wet bulb potential temperature. + * + * @param pres + * - Pressure ( in millibars ) + * @param tmpc + * - Temperature ( in Celsius ) + * @param dwpc + * - Dewpoint ( in Celsius ) + * @return The wet bulb potential temperature ( in Celsius ) if all inputs + * are valid + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prThwc(Amount pres, Amount tmpc, Amount dwpc) + throws InvalidValueException, NullPointerException { + // System.out.println(" PRLibrary/prThwc:"); + // System.out.println(" PRLibrary/prThwc. press = " + + // pres.doubleValue()); + // System.out.println(" PRLibrary/prThwc.tmpc = " + tmpc.doubleValue()); + // System.out.println(" PRLibrary/prThwc.dwpc = " + dwpc.doubleValue()); + + /* Check for missing and invalid data */ + // checkNullOrInvalidValue( pres ); + // checkNullOrInvalidValue( tmpc ); + // checkNullOrInvalidValue( dwpc ); + + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(pres) + || !checkNullOrInvalidValue(dwpc)) + return new Amount(SI.CELSIUS); + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + tmpc = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.CELSIUS); + dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); + + // System.out + // .println(" PRLibrary/prThwc. press 2 = " + pres.doubleValue()); + // System.out.println(" PRLibrary/prThwc.tmpc 2 = " + + // tmpc.doubleValue()); + // System.out.println(" PRLibrary/prThwc.dwpc 2 = " + + // dwpc.doubleValue()); + + double presVal = pres.doubleValue(); + // System.out + // .println(" PRLibrary/prThwc. press 3 = " + pres.doubleValue()); + + if (presVal <= 0) { + System.out + .println("From prThwc: Pressure must be greater than 0 mb"); + return new Amount(SI.CELSIUS); + // throw new + // InvalidRangeException("From prThwc: Pressure must be greater than 0 mb"); + } + /* Compute the thte */ + Amount thte = prThte(pres, tmpc, dwpc); + // System.out.println(" PRLibrary/prThwc. thte 4 = " + + // thte.doubleValue()); + + /* Check for missing 'thte' and compute wet bulb temperature. */ + if (!checkNullOrInvalidValue(thte)) + return new Amount(SI.CELSIUS); + /* Compute the parcel temperature (in Kelvin) */ + + Amount prthwc = prTmst(thte, new Amount(1000, NcUnits.MILLIBAR), + new Amount(0, SI.KELVIN)); + // System.out.println(" PRLibrary/prThwc. prthwc (K) 5 = " + // + prthwc.doubleValue()); + + if (!checkNullOrInvalidValue(prthwc)) + return new Amount(SI.CELSIUS); + /* Convert the parcel temperature to Celsius */ + prthwc = checkAndConvertInputAmountToExpectedUnits(prthwc, SI.CELSIUS); + // System.out.println(" PRLibrary/prThwc. prthwc (C) 6 = " + // + prthwc.doubleValue()); + return prthwc; + } + + /** + * Computes the temperature at the lifted condensation level for a parcel of + * air given the temperature ( in Celsius ) and the dewpoint (in Celsius) + * using the equation: TLCL = [ 1 / ( 1 / (DWPK-56) + ALOG (TMPK/DWPK) / 800 + * ) ] + 56 Bolton. + * + * @param tmpc + * - the temperature ( in Celsius ) + * @param dwpc + * - the dewpoint ( in Celsius ) + * @return the lifted condensation level temperature In Kelvin, if both + * input values are valid + * + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prTlcl(Amount tmpc, Amount dwpc) { + // System.out.println("From prTlcl:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(dwpc); + if (!checkNullOrInvalidValue(tmpc) || !checkNullOrInvalidValue(dwpc)) + return new Amount(SI.KELVIN); + + if (tmpc.doubleValue() < -GempakConstants.TMCK + || dwpc.doubleValue() < -GempakConstants.TMCK) { + System.out + .println("From prTlcl: Input temperature cannot be less than -273.15"); + return new Amount(SI.KELVIN); + } + Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); + Amount dwpk = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.KELVIN); + double tempVal = tmpk.doubleValue(); + double dewpointVal = dwpk.doubleValue(); + double lclTemp = (double) ((800 * (dewpointVal - 56) / (800 + (dewpointVal - 56) + * Math.log(tempVal / dewpointVal))) + 56); + Amount prtlcl = new Amount(lclTemp, SI.KELVIN); + + return prtlcl; + } + + /** + * Computes the temperature ( in Kelvin ) from the pressure ( in mb ) and + * the potential temperature ( in Kelvin ) using the Poisson equation: TMPK + * = THTA * ( PRES / 1000 ) ** RKAPPA + * + * @param pres + * - the pressure ( in mb ) + * @param thta + * - the potential temperature ( in Kelvin ) + * @return the temperature ( in Kelvin ) + * @throws InvalidRangeException + * @throws NullPointerException + * + */ + public static final Amount prTmpk(Amount pres, Amount thta) + throws InvalidValueException, NullPointerException { + // System.out.println("From prTmpk:"); + // System.out.println(" pres = " + pres.doubleValue()); + // System.out.println("thta = " + thta.doubleValue()); + + Amount prtmpk = new Amount(SI.KELVIN); + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(thta)) { + return new Amount(SI.KELVIN); + } + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + thta = checkAndConvertInputAmountToExpectedUnits(thta, SI.KELVIN); + double pressureValue = pres.doubleValue(); + double thtaValue = thta.doubleValue(); + if (pressureValue >= 0) { + double temperature = (double) (thtaValue * (Math.pow( + pressureValue / 1000f, GempakConstants.RKAPPA))); + prtmpk = new Amount(temperature, SI.KELVIN); + return prtmpk; + } else { + System.out + .println("From prTmpk() - pressure cannot be less than 0 mb"); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException("From prTmpk() - pressure cannot be less than 0 mb"); + } + } + + /** + *
+     * Computes the parcel temperature ( in Kelvin ) from the equivalent potential temp ( in Kelvin ),
+     * pressure ( in millibars ) and the first guess temperature ( in Kelvin ). 
+     * The parcel temperature at level pres on a specified moist adiabat ( thte ). 
+     * The computation is an iterative Newton-Raphson technique of the form:
+     * 
+     * x = x(guess) + [ f( x ) - f( x(guess) ) ] / f'( x(guess) )
+     * f' is approximated with finite differences
+     * f' = [ f( x(guess) + 1 ) - f( x(guess) ) ] / 1
+     * 
+     * If tguess is 0, a reasonable first guess will be made.
+     * Convergence is not guaranteed for extreme input values.  If the
+     * computation does not converge after 100 iterations, the missing 
+     * data value will be returned. 
+     * @param thte      - Equivalent potential temp ( in Kelvin )
+     * @param pres      - Pressure ( in millibars )
+     * @param tguess   - First guess temperature ( in Kelvin )
+     * @return the Parcel temperature in Kelvin if all the input values are valid 
+     * (without being extreme) and if a convergence is obtained within 100 iterations
+     * 
+ * + * @throws NullPointerException + * + */ + public static final Amount prTmst(Amount thte, Amount pres, Amount tguess) { + double prtmst = GempakConstants.RMISSD; + // System.out.println(" PRLibrary/prTmst:"); + // System.out.println(" PRLibrary/prTmst. thte = " + + // thte.doubleValue()); + // System.out.println(" PRLibrary/prTmst. pres = " + + // pres.doubleValue()); + // System.out.println(" PRLibrary/prTmst. tguess = " + // + tguess.doubleValue()); + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(thte) + || !checkNullOrInvalidValue(tguess)) { + return new Amount(SI.KELVIN); + } + // checkNullOrInvalidValue( thte ); + // checkNullOrInvalidValue( pres ); + // checkNullOrInvalidValue( tguess ); + thte = checkAndConvertInputAmountToExpectedUnits(thte, SI.KELVIN); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + tguess = checkAndConvertInputAmountToExpectedUnits(tguess, SI.KELVIN); + double thteVal = thte.doubleValue(); + double presVal = pres.doubleValue(); + double tguessVal = tguess.doubleValue(); + + if (thteVal <= 0) { + System.out + .println(" From prTmst(): Potential temperature must be greater than 0"); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException(" From prTmst(): Potential temperature must be greater than 0"); + } else if (presVal <= 0) { + System.out + .println(" From prTmst(): Pressure must be greater than 0"); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException(" From prTmst(): Pressure must be greater than 0"); + } else if (tguessVal < 0) { + System.out + .println(" From prTmst(): First guess temperature must be greater than or equal to 0"); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException(" From prTmst(): First guess temperature must be greater than 0"); + } + double tg = tguess.doubleValue(); + // System.out.println(" PRLibrary/prTmst. thteVal 2 = " + thteVal); + // System.out.println(" PRLibrary/prTmst. presVal 2 = " + presVal); + // System.out.println(" PRLibrary/prTmst. tguessVal 2 = " + tguessVal); + // System.out.println(" PRLibrary/prTmst. tg 2 = " + tg); + + /* + * If tguess is passed as 0. it is computed from an MIT scheme + */ + if (tg == 0) { + double diffVar = thte.doubleValue() - 270; + double mathFormula1 = (double) (diffVar > 0 ? diffVar : 0.0); + tg = (double) ((thte.doubleValue() - .5f * (Math.pow(mathFormula1, + 1.05f))) * (Math.pow(pres.doubleValue() / 1000.0f, 0.2f))); + } + + /* Set convergence and initial guess in degrees Celsius */ + double epsi = 0.01f; + double tgnu = SI.KELVIN.getConverterTo(SI.CELSIUS).convert(tg); + + /* + * Set a limit of 100 iterations. Compute tenu,tenup, the thte's at one + * degree above the guess temperature. + */ + int index = 0; + while (index < 100) { + double tgnup = tgnu + 1; + Amount tgnuAmount = new Amount(tgnu, SI.CELSIUS); + Amount tgnupAmount = new Amount(tgnup, SI.CELSIUS); + Amount tenu = prThte(pres, tgnuAmount, tgnuAmount); + Amount tenup = prThte(pres, tgnupAmount, tgnupAmount); + // System.out.println(" PRLibrary/prTmst. tgnuAmount 5 = " + // + tgnuAmount.getValue().floatValue()); + // System.out.println(" PRLibrary/prTmst. tgnupAmount 5 = " + // + tgnupAmount.getValue().floatValue()); + // System.out.println(" PRLibrary/prTmst. tenu 5 = " + // + tenu.getValue().floatValue()); + // System.out.println(" PRLibrary/prTmst. tgnup 5 = " + // + tenup.getValue().floatValue()); + /* Check that the THTE's exist. */ + + if ((!checkNullOrInvalidValue(tenu) || !checkNullOrInvalidValue(tenup))) { + return new Amount(SI.KELVIN); + // index++; + // continue; + } + + /* Compute the correction */ + double tenuVal = tenu.doubleValue(); + double tenupVal = tenup.doubleValue(); + double cor = (thteVal - tenuVal) / (tenupVal - tenuVal); + tgnu += cor; + // System.out.println(" PRLibrary/prTmst. tenuVal 6 = " + tenuVal); + // System.out.println(" PRLibrary/prTmst. tenupVal 6 = " + + // tenupVal); + + // System.out.println(" PRLibrary/prTmst. cor 6 = " + cor); + // System.out.println(" PRLibrary/prTmst. tgnu 6 = " + tgnu); + + if ((cor < epsi) && (-cor < epsi)) { + + /* return on convergence */ + prtmst = tgnuAmount.getUnit().getConverterTo(SI.KELVIN) + .convert(tgnu); + // System.out.println(" PRLibrary/prTmst. prtmst 7 = " + + // prtmst); + + break; + } + + index++; + } + // System.out.println(" PRLibrary/prTmst. prtmst 8 = " + // + new Amount(prtmst, SI.KELVIN).getValue().floatValue()); + + return new Amount(prtmst, SI.KELVIN); + } + + /** + *
+     * Computes wet bulb temperature from the temperature, mixing ratio, and pressure.
+     * The result is obtained by solving for the temperature at which saturation occurs,
+     *  when the latent heat required to vaporize the water is provided by a cooling of the air.
+     *  The equation representing the process is:
+     *   ( tmpk - tmwb ) * cp - ( Rsat (tmwb) - rmix ) * lvap = 0  
+     *  This implicit equation is solved by Newton's method, since the 
+     *  saturation mixing ratio Rsat is a transcendental function of tmwb.
+     *  The expressions for the heat of vaporization (LVAP) and saturation 
+     *   vapor pressure are equations (2) and (10) from Bolton (MWR, 1980).
+     * 
+ * + * @param tmpk + * - Temperature (K) + * @param rmix + * - Mixing ratio (g/kg) + * @param pres + * - Pressure (mb) + * @return Wet bulb temperature (K) if all inputs are valid + * @throws NullPointerException + * + */ + public static final Amount prTmwb(Amount tmpk, Amount rmix, Amount pres) + throws InvalidValueException, NullPointerException { + // System.out.println("From prTmwb:"); + // System.out.println(" tmpk = " + tmpk.doubleValue()); + // System.out.println(" rmix = " + rmix.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + + Amount prtmwb = null; + /* Check for missing and invalid data */ + // checkNullOrInvalidValue( tmpk ); + // checkNullOrInvalidValue( rmix ); + // checkNullOrInvalidValue( pres ); + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(tmpk) + || !checkNullOrInvalidValue(rmix)) { + return new Amount(SI.KELVIN); + } + tmpk = checkAndConvertInputAmountToExpectedUnits(tmpk, SI.KELVIN); + rmix = checkAndConvertInputAmountToExpectedUnits(rmix, + NcUnits.GRAMS_PER_KILOGRAM); + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + double presVal = pres.doubleValue(); + if (presVal <= 0) { + // System.out + // .println("From prTmwb - pressure value must be greater than 0 "); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException("From prTmwb - pressure value must be greater than 0 "); + } + /* Change temperature to degrees Celsius. */ + Amount tmp = checkAndConvertInputAmountToExpectedUnits(tmpk, SI.CELSIUS); + + /* Compute the latent heat of vaporization. */ + Amount lvap = prLhvp(tmp); + if (!checkNullOrInvalidValue(lvap)) + return new Amount(SI.KELVIN); + /* Compute the specific heat of moist air */ + double rmixVal = rmix.doubleValue() / 1000; + double cp = (1005.7 * (1.0 + 0.887 * rmixVal)); + + double rlocp = lvap.doubleValue() / cp; + + /* Do Newton iteration */ + int iter = 0; + double twb = tmp.doubleValue(); + boolean isConvrg = false; + + double A = 6.112; + double B = 17.67; + double C = 243.5; + double EPSI = 0.622; + double G = B * C; + double ERRMAX = 0.001; + double tmpVal = tmp.doubleValue(); + while (iter <= 50 && !isConvrg) { + iter++; + double bt = B * twb; + double tpc = twb + C; + double d = ((presVal / A) * Math.exp((-bt) / tpc)); + double dm1 = d - 1; + double f = (tmpVal - twb) - rlocp * (EPSI / dm1 - rmixVal); + double df = (-G) / (tpc * tpc); + df = d * df * rlocp * EPSI / (dm1 * dm1) - 1; + double cor = f / df; + twb = twb - cor; + if (Math.abs(cor) <= ERRMAX) + isConvrg = true; + } + + if (isConvrg) { + Amount twk = new Amount(twb, SI.KELVIN); + if (twk.doubleValue() > tmpk.doubleValue()) + twk = new Amount(tmpk.doubleValue(), SI.KELVIN); + + prtmwb = twk; + } + // } + return prtmwb; + } + + /** + * Computes the virtual temperature ( in Kelvin ) from the temperature ( in + * Celsius ), dewpoint ( in Celsius ) and pressure ( in mb ) where DWPC and + * PRES are used to compute MIXR. The following equation is used: TVRK = + * TMPK * (1 + .001 * MIXR / .62197) / (1 + .001 * MIXR) If DWPC is missing, + * dry air is assumed and TMPK is returned. + * + * @param tmpc + * - Temperature ( in Celsius ) + * @param dwpc + * - Dewpoint ( in Celsius ) + * @param pres + * - Pressure ( in mb ) + * @return the virtual temperature ( in Kelvin ) + * @throws NullPointerException + * + */ + public static final Amount prTvrk(Amount tmpc, Amount dwpc, Amount pres) + throws InvalidValueException, NullPointerException { + // System.out.println("From prTvrk:"); + // System.out.println(" tmpc = " + tmpc.doubleValue()); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + + Amount prtvrk = null; + // checkNullOrInvalidValue(tmpc); + // checkNullOrInvalidValue(pres); + + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(tmpc)) { + return new Amount(SI.KELVIN); + } + + /* If dewpoint is missing, return temperature */ + + if (!checkNullOrInvalidValue(dwpc)) + return checkAndConvertInputAmountToExpectedUnits(tmpc, SI.KELVIN); + + else { + /* Change temperature to Kelvin. */ + Amount tmpk = checkAndConvertInputAmountToExpectedUnits(tmpc, + SI.KELVIN); + + /* Find mixing ratio in g/kg; if missing, return temperature */ + Amount rmix = prMixr(dwpc, pres); + + double virtualTemp; + if (rmix.doubleValue() == GempakConstants.RMISSD) + virtualTemp = (double) tmpc.getUnit().getConverterTo(SI.KELVIN) + .convert(tmpc.doubleValue()); + else { + double mixingRatioVal = rmix.doubleValue(); + double temp = tmpk.doubleValue(); + virtualTemp = (double) (temp + * (1 + 0.001 * mixingRatioVal / 0.62197) / (1 + 0.001 * mixingRatioVal)); + + } + prtvrk = new Amount(virtualTemp, SI.KELVIN); + } + return prtvrk; + } + + /** + * Computes the 'U' component of the wind from its speed and direction + * + * @param sped + * - wind speed + * @param drct + * - wind direction + * @return The 'U' component of the wind if both inputs are valid + * @throws NullPointerException + * + */ + public static final Amount prUwnd(Amount sped, Amount drct) + throws InvalidValueException, NullPointerException { + // System.out.println("From prUwnd:"); + // System.out.println(" sped = " + sped.doubleValue()); + // System.out.println(" drct = " + drct.doubleValue()); + if (!checkNullOrInvalidValue(drct) || !checkNullOrInvalidValue(sped)) { + return new Amount(SI.METERS_PER_SECOND); + } + drct = checkAndConvertInputAmountToExpectedUnits(drct, + NonSI.DEGREE_ANGLE); + double pruwnd = ((-Math.sin(drct.doubleValue() * GempakConstants.DTR)) * sped + .doubleValue()); + return new Amount(pruwnd, sped.getUnit()); // TODO :verify the units + } + + /** + * Computes the 'V' component of the wind from its speed and direction + * + * @param sped + * - wind speed + * @param drct + * - wind direction + * @return The 'V' component of the wind if both inputs are valid + * @throws NullPointerException + * + */ + public static final Amount prVwnd(Amount sped, Amount drct) + throws InvalidValueException, NullPointerException { + // System.out.println("From prVwnd:"); + // System.out.println(" sped = " + sped.doubleValue()); + // System.out.println(" drct = " + drct.doubleValue()); + if (!checkNullOrInvalidValue(drct) || !checkNullOrInvalidValue(sped)) { + return new Amount(SI.METERS_PER_SECOND); + } + drct = checkAndConvertInputAmountToExpectedUnits(drct, + NonSI.DEGREE_ANGLE); + double prvwnd = ((-Math.cos(drct.doubleValue() * GempakConstants.DTR)) * sped + .doubleValue()); + return new Amount(prvwnd, sped.getUnit()); // TODO :verify the units + } + + /** + * Computes the vapor pressure ( in mb) from the input dewpoint temperature + * in Celsius using the equation: VAPR = 6.112 * EXP [ (17.67 * + * dewpointValue) / (dewpointValue + 243.5) ] + * + * @param dwpc + * - the dewpoint temperature ( in Celsius ) + * @return the vapor pressure ( in mb) from the dewpoint temperature if it + * is valid + * @throws NullPointerException + * + */ + public static final Amount prVapr(Amount dwpc) { + // System.out.println("From prVapr:"); + // System.out.println(" dwpc = " + dwpc.doubleValue()); + + if (!checkNullOrInvalidValue(dwpc)) + return new Amount(NcUnits.MILLIBAR); + dwpc = checkAndConvertInputAmountToExpectedUnits(dwpc, SI.CELSIUS); + double dewpointValue = dwpc.doubleValue(); + if (dewpointValue >= -240.0f) + return (new Amount((6.112 * (Math.exp((17.67 * dewpointValue) + / (dewpointValue + 243.5)))), NcUnits.MILLIBAR)); + else { + // throw new + // InvalidRangeException("Exception from prVapr() - dewpoint cannot be less than -240 "); + System.out + .println("From prVapr() - dewpoint cannot be less than -240 "); + return new Amount(NcUnits.MILLIBAR); + } + } + + // /** + // * Computes the visibility ( in nautical miles ) from the input visibility + // ( in kilometers ) + // * @param vsbk - visibility ( in kilometers ) + // * @return visibility ( in nautical miles ) if the input is valid + // * @throws NullPointerException + // * + // */ + // //TODO: remove this - since the getValueAs(Unit) offers the same + // facility? + // public static final Amount prVskn ( Amount vsbk) throws + // InvalidValueException, NullPointerException { + // //System.out.println("From prVskn:"); + // //System.out.println(" vsbk = " + vsbk.doubleValue()); + // + // checkNullOrInvalidValue( vsbk ); + // vsbk = checkAndConvertInputAmountToExpectedUnits( vsbk, SI.KILOMETER ); + // return ( checkAndConvertInputAmountToExpectedUnits(vsbk, + // NonSI.NAUTICAL_MILE ) ); + // } + + /** + * Computes the wind chill equivalent temperature ( the temperature with + * calm winds that produces the same cooling effect as the given temperature + * with the given wind speed) + * + * @param tmpf + * - Air temperature ( in Farenheit ) + * @param sknt + * - Wind speed ( in knots ) + * @return the wind chill equivalent temperature ( in Farenheit ), if the + * inputs are valid + * @throws NullPointerException + * + */ + public static final Amount prWceq(Amount tmpf, Amount sknt) + throws InvalidValueException, NullPointerException { + // System.out.println("From prWceq:"); + // System.out.println(" tmpf = " + tmpf.doubleValue()); + // System.out.println(" sknt = " + sknt.doubleValue()); + + double prwceq = GempakConstants.RMISSD; + if (!checkNullOrInvalidValue(tmpf) || !checkNullOrInvalidValue(sknt)) { + return new Amount(NonSI.FAHRENHEIT); + } + + /* Convert input variables to Celsius and meters/second. */ + Amount tmpc = checkAndConvertInputAmountToExpectedUnits(tmpf, + SI.CELSIUS); + Amount sped = checkAndConvertInputAmountToExpectedUnits(sknt, + SI.METERS_PER_SECOND); + + if (sped.doubleValue() <= 1.34) + /* + * If the wind speed does not exceed 1.34 m/s ( not much wind to + * contribute to the wind chill), return the input temperature as + * the wind chill temperature + */ + prwceq = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT) + .convert(tmpc.doubleValue()); + else { + /* + * Compute the wind chill temp if the inputs are not missing and and + * the wind speed is greater than 1.34 m/s. Equations for wind chill + * computation from R. Falconer, + * "Windchill, A Useful Wintertime Weather Variable", Weatherwise, + * Dec 1968. + */ + if (sped.getUnit() == SI.METERS_PER_SECOND) { + float windChill = (float) (33.0 - ((33.0 - tmpc.doubleValue()) + * wci(sped.doubleValue()) / wci(1.34f))); + prwceq = tmpc.getUnit().getConverterTo(NonSI.FAHRENHEIT) + .convert(windChill); + } + } + + return (new Amount(prwceq, NonSI.FAHRENHEIT)); + } + + // /** + // * Computes the numeric total cloud cover for the worst case aviation + // flight condition, + // * based on the categorical identification of flight rules for prevailing + // and temporary / probability conditions. + // * @param xvfr - Prevailing categorical id of flight rules + // * @param txvf - Temporary / Probability categorical id of flight rules + // * @param cfrt - Prevailing numeric total cloud cover + // * @param tcfr - Temporary / Probability numeric total cloud cover + // * @return Worst case numeric total cloud cover or RMISSD (-9999) if the + // computation does not fall through + // * @throws NullPointerException + // * + // */ + // public static final Amount prWcfr ( Amount xvfr, Amount txvf, Amount + // cfrt, Amount tcfr) throws InvalidValueException, NullPointerException { + // double prwcfr = GempakConstants.RMISSD; + // checkNullOrInvalidValue( tcfr ); + // checkNullOrInvalidValue( cfrt ); + // double cfrtVal = cfrt.doubleValue(); + // double tcfrVal = tcfr.doubleValue(); + // if ( ( xvfr == null || xvfr.doubleValue() == GempakConstants.RMISSD ) + // || ( txvf == null || txvf.doubleValue() == GempakConstants.RMISSD )) + // prwcfr = ( cfrtVal > tcfrVal ? cfrtVal : tcfrVal ); + // + // else { + // double txvfVal = txvf.doubleValue(); + // double xvfrVal = xvfr.doubleValue(); + // if ( txvfVal < xvfrVal ) + // prwcfr = tcfrVal; + // else if ( txvfVal == xvfrVal) + // prwcfr = ( cfrtVal > tcfrVal ? cfrtVal : tcfrVal ); + // else + // prwcfr = cfrtVal; + // } + // return ( new Amount ( prwcfr ,Unit.ONE)); + // } + + /** + * Computes the wind chill temperature from the air temperature and the wind + * speed + * + * @param tmpf + * - Air temperature ( in degree Farenheit ) + * @param sknt + * - Wind speed ( in knots ) + * @return the wind chill temperature ( in Farenheit ) if none of the inputs + * are missing + * @throws NullPointerException + * + */ + public static final Amount prWcht(Amount tmpf, Amount sknt) + throws InvalidValueException, NullPointerException { + // System.out.println("From prWcht:"); + // System.out.println(" tmpf = " + tmpf.doubleValue()); + // System.out.println(" sknt = " + sknt.doubleValue()); + double prwrcht = GempakConstants.RMISSD; + if (!checkNullOrInvalidValue(tmpf) || !checkNullOrInvalidValue(sknt)) { + return new Amount(NonSI.FAHRENHEIT); + } + + /* Convert the speed to miles per hour */ + Amount smph = checkAndConvertInputAmountToExpectedUnits(sknt, + NonSI.MILES_PER_HOUR); + + /* + * If the inputs are not missing , check if the wind speed is <= 3 miles + * per hour + */ + + double smphVal = smph.doubleValue(); + double tmpfVal = tmpf.doubleValue(); + if (smphVal <= 3) + prwrcht = tmpfVal; + else { + /* + * Compute the wind-chill temperature for wind speeds that exceed 3 + * miles per hour + */ + float wcht = (float) (35.74 + 0.6215 * tmpfVal - 35.75 + * Math.pow(smphVal, 0.16) + 0.4275 * tmpfVal + * Math.pow(smphVal, 0.16)); + prwrcht = (wcht > tmpfVal ? tmpfVal : wcht); + } + return (new Amount(prwrcht, NonSI.FAHRENHEIT)); + } + + /** + * Computes the wind component towards a specific direction from the wind + * direction, wind speed and direction of desired component. + * + * @param drct + * - the wind direction in degrees + * @param sped + * - the wind speed in m/s + * @param dcmp + * - the direction of the desired component + * @return the component of the wind (in m/s) if none of the input + * parameters are missing + * @throws NullPointerException + * + */ + public static final Amount prWcmp(Amount drct, Amount sped, Amount dcmp) + throws InvalidValueException, NullPointerException { + // System.out.println("From prWcmp:"); + // System.out.println(" sped = " + sped.doubleValue()); + // System.out.println(" drct = " + drct.doubleValue()); + // System.out.println(" dcmp = " + dcmp.doubleValue()); + /* Check for missing input parameters */ + // checkNullOrInvalidValue( drct ); + // checkNullOrInvalidValue( sped ); + // checkNullOrInvalidValue( dcmp ); + if (!checkNullOrInvalidValue(drct) || !checkNullOrInvalidValue(sped) + || !checkNullOrInvalidValue(dcmp)) { + return new Amount(SI.METERS_PER_SECOND); + } + + drct = checkAndConvertInputAmountToExpectedUnits(drct, + NonSI.DEGREE_ANGLE); + dcmp = checkAndConvertInputAmountToExpectedUnits(dcmp, + NonSI.DEGREE_ANGLE); + sped = checkAndConvertInputAmountToExpectedUnits(sped, + SI.METERS_PER_SECOND); + + /* Calculate wind speed toward specified direction */ + double prwcmp = sped.doubleValue() + * (-Math.cos((drct.doubleValue() - dcmp.doubleValue()) + * GempakConstants.DTR)); + + return new Amount(prwcmp, SI.METERS_PER_SECOND); + } + + /** + * Computes the wind component toward a direction 90 degrees + * counterclockwise of a specified direction. If no direction is specified, + * the component toward north is returned. + * + * @param drct + * - wind direction ( in degrees ) + * @param sped + * - wind speed ( in knots or m/s ) + * @param dcmp + * - specified wind direction ( in degrees ) + * @return a component of the wind in m/s if the input wind speed and + * direction are valid and if the specified wind direction is + * between 0 degrees and 360 degrees. + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prWnml(Amount drct, Amount sped, Amount dcmp) + throws InvalidValueException, NullPointerException { + // System.out.println("From prWnml:"); + // System.out.println(" sped = " + sped.doubleValue()); + // System.out.println(" drct = " + drct.doubleValue()); + // System.out.println(" dcmp = " + dcmp.doubleValue()); + // checkNullOrInvalidValue(sped); + // checkNullOrInvalidValue(drct); + // checkNullOrInvalidValue(dcmp); + if (!checkNullOrInvalidValue(drct) || !checkNullOrInvalidValue(sped) + || !checkNullOrInvalidValue(dcmp)) { + return new Amount(SI.METERS_PER_SECOND); + } + + drct = checkAndConvertInputAmountToExpectedUnits(drct, + NonSI.DEGREE_ANGLE); + dcmp = checkAndConvertInputAmountToExpectedUnits(dcmp, + NonSI.DEGREE_ANGLE); + sped = checkAndConvertInputAmountToExpectedUnits(sped, + SI.METERS_PER_SECOND); + if ((dcmp.doubleValue() < 0) && (dcmp.doubleValue() > 360)) { + // throw new + // InvalidRangeException("From prWnml - the wind direction 'dcmp' mus be greater than or equal to 0 and less than or equal to 360"); + System.out + .println("From prWnml - the wind direction 'dcmp' mus be greater than or equal to 0 and less than or equal to 360"); + return new Amount(SI.METERS_PER_SECOND); + } + /* + * Calculate wind speed 90 degrees to left of given direction. + */ + double prwnml = (float) (sped.doubleValue() * (-Math.cos((drct + .doubleValue() - dcmp.doubleValue() - 90) + * GempakConstants.DTR))); + return (new Amount(prwnml, SI.METERS_PER_SECOND)); + } + + // /** + // * Computes the packed wind speed and direction from the input wind speed + // and wind direction + // * @param drct - wind direction ( in degrees ) + // * @param sped - wind speed ( in knots or m/s ) + // * @return the packed speed and direction + // */ + // public static final Amount prWind ( Amount drct, Amount sped ){ + // float prwind = GempakConstants.RMISSD; + // // if ( !MissingValueTester.isDataValueMissing(drct) + // // && !MissingValueTester.isDataValueMissing(sped)){ + // /* + // * (Non-Javadoc) + // * The packed wind speed and direction are of the form: + // * SSSDDD, where SSS - wind speed ( in knots or m/s ) and + // * DDD - wind direction in degrees + // * + // */ + // int jdrct = (int ) Math.round( drct.doubleValue() ); + // int jsped = (int) Math.round( sped.doubleValue() ); + // prwind = jdrct + jsped * 1000; + // // } + // return prwind; + // } + + /** + * Computes the worst case categorical identification of flight rules for + * prevailing and temporary / probability conditions. + * + * @param xvfr + * - Prevailing categorical id of flight rules + * @param txvf + * - Temporary / probability categorical id of flight rules + * @return The worst case categorical id of flight rules + * @throws NullPointerException + * + */ + public static final Amount prWxvf(Amount xvfr, Amount txvf) + throws InvalidValueException, NullPointerException { + // System.out.println("From prWxvf:"); + // System.out.println(" xvfr = " + xvfr.doubleValue()); + // System.out.println(" txvf = " + txvf.doubleValue()); + + double prwxvf = GempakConstants.RMISSD; + if (txvf != null && xvfr != null) { + + double xvfrVal = xvfr.doubleValue(); + double txvfVal = txvf.doubleValue(); + + if (txvfVal != GempakConstants.RMISSD + && xvfrVal != GempakConstants.RMISSD) + prwxvf = (xvfrVal < txvfVal ? xvfrVal : txvfVal); + + else if (xvfrVal == GempakConstants.RMISSD + && txvfVal != GempakConstants.RMISSD) + prwxvf = xvfrVal; + + else if (txvfVal != GempakConstants.RMISSD + && xvfrVal != GempakConstants.RMISSD) + prwxvf = txvfVal; + } + + return (new Amount(prwxvf, Unit.ONE)); + } + + /** + *
+     * Computes LIFR/IFR/MVFR/VFR flight conditions based on ceiling and visibility.
+     * @param ceil - Ceiling in hundreds of feet
+     * @param vsby - Visibility in statute miles
+     * @return Flight conditions index value: 
+     *     0 - LIFR
+     *     1 - IFR
+     *     2 - MVFR
+     *     3 - VFR
+     * 
+ * + * @throws NullPointerException + * + */ + public static final Amount prXvfr(Amount ceil, Amount vsby) + throws InvalidValueException, NullPointerException { + // System.out.println("From prXvfr:"); + // System.out.println(" ceil = " + ceil.doubleValue()); + // System.out.println(" vsby = " + vsby.doubleValue()); + + double prxvfr = GempakConstants.RMISSD; + double vc = GempakConstants.RMISSD; + double vs = GempakConstants.RMISSD; + if (vsby == null) + return null; + + if (!checkNullOrInvalidValue(ceil)) + return new Amount(Unit.ONE); + ceil = checkAndConvertInputAmountToExpectedUnits(ceil, + NcUnits.HUNDREDS_OF_FEET); + vsby = checkAndConvertInputAmountToExpectedUnits(vsby, NonSI.MILE); + /* Compute categorical flight rules */ + + // Check the ceiling value + + double ceilVal = ceil.doubleValue(); + + double vsbyVal = vsby.doubleValue(); + + if (ceilVal < 0) { + // no-op. So vc retains its RMISSD value + } else if (ceilVal < 5) + vc = 0; + else if (ceilVal < 10) + vc = 1; + else if (ceilVal <= 30) + vc = 2; + else if ((vsbyVal > 5) || (vsbyVal < 0) + || (vsbyVal == GempakConstants.RMISSD)) { + prxvfr = 3; + } + + /* Check the visibility value. */ + if (vsbyVal != GempakConstants.RMISSD) { + if (vsbyVal < 0) { + // no-op. So vs retains it RMISSD value + } else if (vsbyVal < 1) + vs = 0; + else if (vsbyVal < 3) + vs = 1; + else if (vsbyVal <= 5) + vs = 2; + else + vs = 3; + } + + /* Determine the more restrictive of the two values. */ + if (vc == GempakConstants.RMISSD) + prxvfr = vs; + else if (vs == GempakConstants.RMISSD) + prxvfr = vc; + else + prxvfr = (vc < vs ? vc : vs); + + return (new Amount(prxvfr, Unit.ONE)); + + } + + /** + * Computes station elevation from altimeter and station pressure. It is + * also used to estimate height at various pressure levels from the + * altimeter in millibars. The PC library computes zmsl, Z000, Z950, Z850, + * Z800 by calling this function with pres equal to PMSL, 1000, 950, 850 and + * 800 respectively. + * + * @param altm + * - Altimeter in millibars + * @param pres + * - Pressure in millibars + * @return the height ( in meters ) if neither input value is missing and + * both input values are greater than zero. + * @throws NullPointerException + * + * @throws InvalidRangeException + */ + public static final Amount prZalt(Amount altm, Amount pres) + throws InvalidValueException, NullPointerException { + // System.out.println("From prZalt:"); + // System.out.println(" altm = " + altm.doubleValue()); + // System.out.println(" pres = " + pres.doubleValue()); + + // checkNullOrInvalidValue( pres ); + // checkNullOrInvalidValue( altm ); + + if (!checkNullOrInvalidValue(pres) || !checkNullOrInvalidValue(altm)) { + return new Amount(SI.METER); + } + + pres = checkAndConvertInputAmountToExpectedUnits(pres, NcUnits.MILLIBAR); + altm = checkAndConvertInputAmountToExpectedUnits(altm, NcUnits.MILLIBAR); + if (altm.doubleValue() <= 0) { + // throw new + // InvalidRangeException("From prZalt: altm must be greater than 0 mb"); + System.out.println("From prZalt: altm must be greater than 0 mb"); + return new Amount(SI.METER); + } + if (pres.doubleValue() <= 0) { + // throw new + // InvalidRangeException("From prZalt: pres must be greater than 0 mb"); + System.out.println("From prZalt: pres must be greater than 0 mb"); + return new Amount(SI.METER); + } + double to = GempakConstants.TMCK + 15; + double gamma = GempakConstants.GAMUSD / 1000; + + /* Calculate the exponent and pressure ratio. */ + double expo = (gamma * GempakConstants.RDGAS) / GempakConstants.GRAVTY; + double prat = pres.doubleValue() / altm.doubleValue(); + double przalt = (to * (1 - Math.pow(prat, expo))) / gamma; + + return (new Amount(przalt, SI.METER)); + } + + /** + * Computes the windchill from the wind velocity ( part of the Falconer + * equation - refer method prWceq) + * + * @param d + * - wind velocity ( in meters per second ) + * @return the windchill temperature + */ + private static double wci(double d) { + + /* + * from R. Falconer, "Windchill, A Useful Wintertime Weather Variable", + * Weatherwise, Dec 1968. + */ + return ((double) (10 * Math.sqrt(d) + 10.45 - d)); + + } + + public static final Amount checkAndConvertInputAmountToExpectedUnits( + Amount amountIn, Unit expectedUnit) { + Amount amountOut = null; + if (!amountIn.getUnit().equals(expectedUnit) + && amountIn.getUnit().isCompatible(expectedUnit)) { + double newValue = amountIn.getUnit().getConverterTo(expectedUnit) + .convert(amountIn.doubleValue()); + amountOut = new Amount(newValue, expectedUnit); + } else + // throw new ConversionException("Unable to convert " + + // amountIn.getUnit().toString() + " to " + expectedUnit.toString() + // ); + amountOut = amountIn; + + return amountOut; + } + + // public static final void checkNullOrInvalidValue( Amount amountToCheck ) + // throws InvalidValueException, NullPointerException{ + // if (amountToCheck == null ) + // throw new NullPointerException(); + // else { + // double amountValue = amountToCheck.doubleValue(); + // if ( amountValue == GempakConstants.RMISSD){ + // throw new InvalidValueException( new String + // ("Input amount cannot be -9999")); + // } + // else if ( Double.isNaN(amountValue)) + // throw new InvalidValueException( new String ("Input amount cannot be NaN" + // )); + // } + // } + + public static final boolean checkNullOrInvalidValue(Amount amountToCheck) { + if (amountToCheck == null) { + return false; + // throw new NullPointerException(); + } else { + double amountValue = amountToCheck.doubleValue(); + if (amountValue == GempakConstants.RMISSD) { + System.out.println("Input amount cannot be -9999"); + return false; + // throw new InvalidValueException( new String + // ("Input amount cannot be -9999")); + } else if (Double.isNaN(amountValue)) { + System.out.println("Input amount cannot be NaN"); + // throw new InvalidValueException( new String + // ("Input amount cannot be NaN" )); + return false; + } else + return true; + } + } + + // public static final class InvalidRangeException extends Exception { + // /** + // * + // */ + // private static final long serialVersionUID = -4962228676211688262L; + // + // /** + // * + // */ + // public InvalidRangeException( String msg) { + // super( msg ); + // } + // } + + public static final class InvalidValueException extends Exception { + /** * */ - private static final long serialVersionUID = 3844655201015825508L; + private static final long serialVersionUID = 3844655201015825508L; - /** + /** * */ - public InvalidValueException( String msg) { - super( msg ); - } -} + public InvalidValueException(String msg) { + super(msg); + } + } - - /** -* This function computes parcel PRES from THTE and TMPC, where TMPC -* is the parcel temperature at PRES on a specified moist adiabat -* (THTE). The computation is an iterative Newton-Raphson technique -* of the form: -* -* x = x(guess) + [ f( x ) - f( x(guess) ) ] / f'( x(guess) ) -* -* f' is approximated with finite differences -* f' = [ f( x(guess) + 1 ) - f( x(guess) ) ] / 1 -* -* Convergence is not guaranteed for extreme input values. If the -* computation does not converge after 100 iterations, the missing -* data value will be returned. -* @param thte - Equivalent potential temp in K -* @param tmpk - Parcel temperature in Kelvin -* @return Pressure in millibars -* -* @throws NullPointerException -* @throws InvalidRangeException -*/ + /** + * This function computes parcel PRES from THTE and TMPC, where TMPC is the + * parcel temperature at PRES on a specified moist adiabat (THTE). The + * computation is an iterative Newton-Raphson technique of the form: + * + * x = x(guess) + [ f( x ) - f( x(guess) ) ] / f'( x(guess) ) + * + * f' is approximated with finite differences f' = [ f( x(guess) + 1 ) - f( + * x(guess) ) ] / 1 + * + * Convergence is not guaranteed for extreme input values. If the + * computation does not converge after 100 iterations, the missing data + * value will be returned. + * + * @param thte + * - Equivalent potential temp in K + * @param tmpk + * - Parcel temperature in Kelvin + * @return Pressure in millibars + * + * @throws NullPointerException + * @throws InvalidRangeException + */ - public static Amount prPmst(Amount thte, Amount tmpk) throws InvalidValueException, NullPointerException { -// checkNullOrInvalidValue(thte); -// checkNullOrInvalidValue(tmpk); - - if ( !checkNullOrInvalidValue( thte ) - || !checkNullOrInvalidValue( tmpk ) ){ - return new Amount (SI.KELVIN ); - } - - thte = checkAndConvertInputAmountToExpectedUnits(thte, SI.KELVIN); - tmpk = checkAndConvertInputAmountToExpectedUnits(tmpk, SI.KELVIN); - - if ( thte.getValue().doubleValue() <= 0){ - System.out.println("From prPmst(): The equivalent potential temperature must be greater than 0"); - return new Amount ( SI.KELVIN ); -// throw new InvalidRangeException("The equivalent potential temperature must be greater than 0"); - } - Amount prpmst = new Amount(NcUnits.MILLIBAR); - /*Set convergence and initial guess of pressure.*/ - double epsi = 0.01; - Amount tmpc = new Amount ( tmpk.getValueAs(SI.CELSIUS), SI.CELSIUS ); - double tempVal = 1000 * Math.pow ( tmpk.getValue().doubleValue() / thte.getValue().doubleValue(), GempakConstants.AKAPPA ); - Amount pgdn = new Amount ( tempVal, NcUnits.MILLIBAR ); - boolean done = false; - int i = 1; - while ( !done ) { -// Amount pgdn = new Amount ( tempVal, NcUnits.MILLIBAR ); - Amount pgup = new Amount ( pgdn.getValueAs(NcUnits.MILLIBAR).doubleValue() + 1, NcUnits.MILLIBAR ); - - Amount tedn = prThte ( pgdn, tmpc, tmpc); - Amount teup = prThte ( pgup, tmpc, tmpc); - - if ( ! tedn.hasValidValue() || ! teup.hasValidValue() ) - return prpmst; - - /*Compute the correction; return on convergence.*/ - double cor = ( thte.getValueAs (SI.KELVIN ).doubleValue() - - tedn.getValueAs (SI.KELVIN ).doubleValue() ) / ( teup.getValueAs (SI.KELVIN ).doubleValue() - - tedn.getValueAs (SI.KELVIN ).doubleValue() ); - - double pgdnVal = ( pgdn.getValueAs(NcUnits.MILLIBAR ).doubleValue() + cor ); - pgdn = new Amount (pgdnVal, NcUnits.MILLIBAR); - if ( Math.abs( cor ) < epsi ){ - prpmst = new Amount ( pgdnVal, NcUnits.MILLIBAR ) ; - return prpmst; - } - - i++; - if ( i > 100 ) - done = true; - } - return prpmst; - } + public static Amount prPmst(Amount thte, Amount tmpk) + throws InvalidValueException, NullPointerException { + // checkNullOrInvalidValue(thte); + // checkNullOrInvalidValue(tmpk); + if (!checkNullOrInvalidValue(thte) || !checkNullOrInvalidValue(tmpk)) { + return new Amount(SI.KELVIN); + } + thte = checkAndConvertInputAmountToExpectedUnits(thte, SI.KELVIN); + tmpk = checkAndConvertInputAmountToExpectedUnits(tmpk, SI.KELVIN); + if (thte.getValue().doubleValue() <= 0) { + // System.out + // .println("From prPmst(): The equivalent potential temperature must be greater than 0"); + return new Amount(SI.KELVIN); + // throw new + // InvalidRangeException("The equivalent potential temperature must be greater than 0"); + } + Amount prpmst = new Amount(NcUnits.MILLIBAR); + /* Set convergence and initial guess of pressure. */ + double epsi = 0.01; + Amount tmpc = new Amount(tmpk.getValueAs(SI.CELSIUS), SI.CELSIUS); + double tempVal = 1000 * Math.pow(tmpk.getValue().doubleValue() + / thte.getValue().doubleValue(), GempakConstants.AKAPPA); + Amount pgdn = new Amount(tempVal, NcUnits.MILLIBAR); + boolean done = false; + int i = 1; + while (!done) { + // Amount pgdn = new Amount ( tempVal, NcUnits.MILLIBAR ); + Amount pgup = new Amount(pgdn.getValueAs(NcUnits.MILLIBAR) + .doubleValue() + 1, NcUnits.MILLIBAR); + + Amount tedn = prThte(pgdn, tmpc, tmpc); + Amount teup = prThte(pgup, tmpc, tmpc); + + if (!tedn.hasValidValue() || !teup.hasValidValue()) + return prpmst; + + /* Compute the correction; return on convergence. */ + double cor = (thte.getValueAs(SI.KELVIN).doubleValue() - tedn + .getValueAs(SI.KELVIN).doubleValue()) + / (teup.getValueAs(SI.KELVIN).doubleValue() - tedn + .getValueAs(SI.KELVIN).doubleValue()); + + double pgdnVal = (pgdn.getValueAs(NcUnits.MILLIBAR).doubleValue() + cor); + pgdn = new Amount(pgdnVal, NcUnits.MILLIBAR); + if (Math.abs(cor) < epsi) { + prpmst = new Amount(pgdnVal, NcUnits.MILLIBAR); + return prpmst; + } + + i++; + if (i > 100) + done = true; + } + return prpmst; + } } - - - - - - - - - diff --git a/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PSLibrary.java b/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PSLibrary.java index 71f1331684..5e334dab88 100644 --- a/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PSLibrary.java +++ b/ncep/gov.noaa.nws.ncep.edex.common/src/gov/noaa/nws/ncep/edex/common/metparameters/parameterconversion/PSLibrary.java @@ -3,388 +3,490 @@ */ package gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion; -import java.util.Comparator; - -import java.util.List; +import gov.noaa.nws.ncep.edex.common.metparameters.Amount; +import gov.noaa.nws.ncep.edex.common.metparameters.PressureLevel; +import gov.noaa.nws.ncep.edex.common.metparameters.WetBulbPotentialTemp; +import gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion.PRLibrary.InvalidValueException; +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer2; import java.util.Collections; +import java.util.Comparator; +import java.util.List; import javax.measure.unit.NonSI; import javax.measure.unit.SI; import javax.measure.unit.Unit; -import gov.noaa.nws.ncep.edex.common.metparameters.AirTemperature; -import gov.noaa.nws.ncep.edex.common.metparameters.Amount; -import gov.noaa.nws.ncep.edex.common.metparameters.DewPointTemp; -import gov.noaa.nws.ncep.edex.common.metparameters.PressureLevel; -import gov.noaa.nws.ncep.edex.common.metparameters.WetBulbPotentialTemp; ////import gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion.PRLibrary.InvalidRangeException; -import gov.noaa.nws.ncep.edex.common.metparameters.parameterconversion.PRLibrary.InvalidValueException; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer2; /** * @author archana - * + * */ public final class PSLibrary { - /** - * Computes the cross totals index - * @param td850 - dewpoint at 850 mb ( in Celsius ) - * @param t500 - temperature at 500 mb ( in Celsius ) - * @return the difference between the dewpoint and the temperature if neither of them are missing - * and RMISSD ( -9999 ) otherwise. - * @throws InvalidValueException - * @throws NullPointerException - */ - - public final static Amount psCtot ( Amount td850, Amount t500) { + /** + * Computes the cross totals index + * + * @param td850 + * - dewpoint at 850 mb ( in Celsius ) + * @param t500 + * - temperature at 500 mb ( in Celsius ) + * @return the difference between the dewpoint and the temperature if + * neither of them are missing and RMISSD ( -9999 ) otherwise. + * @throws InvalidValueException + * @throws NullPointerException + */ - /* - * Compute the cross totals index by subtracting 500 mb - * temperature from the 850 mb dewpoint. - */ -if ( !PRLibrary.checkNullOrInvalidValue( td850 ) - || ! PRLibrary.checkNullOrInvalidValue( t500 ) ){ - return new Amount ( Unit.ONE ); - } - Amount psCtot = new Amount( td850.getValue().floatValue() - t500.getValue().floatValue() , Unit.ONE ); - return psCtot; - } - - /** - * Computes low, middle, and high elevation Haines indices - * from the temperature and the dewpoint. - * @param tc1 - temperature ( in Celsius ) - * @param tc2 - temperature ( in Celsius ) - * @param dwpc - dewpoint ( in Celsius ) - * @param itype - Haines index: - * 1- Low - * 2 - Middle - * 3 - High - * - * @return - * @throws InvalidValueException - * @throws NullPointerException - */ - public static Amount psHans ( Amount tc1Amt, Amount tc2Amt, Amount dwpcAmt, Amount itypeAmt) { - Amount pshans =new Amount(); - if ( ! PRLibrary.checkNullOrInvalidValue( tc1Amt ) - || !PRLibrary.checkNullOrInvalidValue( tc2Amt ) - || !PRLibrary.checkNullOrInvalidValue( dwpcAmt ) - || !PRLibrary.checkNullOrInvalidValue( itypeAmt ) ) - return new Amount ( Unit.ONE ); - float a = GempakConstants.RMISSD; - float b = GempakConstants.RMISSD; - float tc1 = tc1Amt.getValue().floatValue(); - float tc2 = tc2Amt.getValue().floatValue(); - float dwpc = dwpcAmt.getValue().floatValue(); - float itype = itypeAmt.getValue().floatValue(); - /* Compute the Haines index*/ - if ( itype == 1 ) { - a = ( ( tc2 - tc1 ) - 3 ) * ( 2 / 5 ) + 1; - b = ( ( tc1 - dwpc ) - 5 ) * ( 2 / 5 ) + 1; - } - else if ( itype == 2 ) { - a = ( ( tc1 - tc2 ) - 5 ) * ( 2 / 6 ) + 1; - b = ( ( tc1 - dwpc ) - 5 ) * ( 2 / 8 ) + 1; - } - else if ( itype == 3 ) { - a = ( ( tc1 - tc2 ) - 17 ) * ( 2 / 5 ) + 1; - b = ( ( tc1 - dwpc ) - 14 ) * ( 2 / 7 ) + 1; - } - - a = ( a > 0.9f ? a : 0.9f ); - a = ( a < 3.1f ? a : 3.1f ); - b = ( b > 0.9f ? b : 0.9f ); - b = ( b < 3.1f ? b : 3.1f ); - pshans = new Amount ( a + b,Unit.ONE ); -// } - return pshans; - } + public final static Amount psCtot(Amount td850, Amount t500) { - /** - * - * Computes the 'K' index - * @param t850 - 850 mb temperature ( in Celsius ) - * @param t700 - 700 mb temperature ( in Celsius ) - * @param t500 - 500 mb temperature ( in Celsius ) - * @param td850 - 850 mb dewpoint ( in Celsius ) - * @param td700 - 700 mb dewpoint ( in Celsius ) - * @return returns the 'K' index if all the input values are valid and - * RMISSD ( -9999 ) otherwise - * @throws InvalidValueException - * @throws NullPointerException - */ - public final static Amount pskinx ( Amount t850, Amount t700, Amount t500, Amount td850, Amount td700) { - Amount pskinx = new Amount(); - if ( !PRLibrary.checkNullOrInvalidValue(t850) - || !PRLibrary.checkNullOrInvalidValue(t700) - || !PRLibrary.checkNullOrInvalidValue(t500) - || !PRLibrary.checkNullOrInvalidValue(td850) - || !PRLibrary.checkNullOrInvalidValue(td700) ) - return new Amount ( Unit.ONE ); - pskinx = new Amount ( ( t850.getValue().floatValue() - t500.getValue().floatValue() ) - + td850.getValue().floatValue() - ( t700.getValue().floatValue() - td700.getValue().floatValue() ) , Unit.ONE); + /* + * Compute the cross totals index by subtracting 500 mb temperature from + * the 850 mb dewpoint. + */ + if (!PRLibrary.checkNullOrInvalidValue(td850) + || !PRLibrary.checkNullOrInvalidValue(t500)) { + return new Amount(Unit.ONE); + } + Amount psCtot = new Amount(td850.getValue().floatValue() + - t500.getValue().floatValue(), Unit.ONE); + return psCtot; + } - return pskinx; - } - - - /** - * Computes the Showalter index - * @param t850 - 850 mb temperature ( in Celsius ) - * @param td850 - 850 mb dewpoint ( in Celsius ) - * @param t500 - 500 mb temperature ( in Celsius ) - * @return the Showalter index if all the three input parameters are valid and the parcel temperature is computed correctly. - * Otherwise, it returns RMISSD (-9999). - */ - public final static Amount psShow ( Amount t850Amt, Amount td850Amt, Amount t500Amt ) { - float psshow = GempakConstants.RMISSD; - - if ( !PRLibrary.checkNullOrInvalidValue(t850Amt) - || !PRLibrary.checkNullOrInvalidValue(td850Amt) - || !PRLibrary.checkNullOrInvalidValue(t500Amt) ) - return new Amount ( Unit.ONE ); - float p850 = 850; - float p500 = 500; - float guess = 0; - /* - * Find equivalent potential temperature at the LCL using 850 mb - * temperature and dewpoint. - */ - Amount thtlcl = PRLibrary.prThte ( new Amount ( p850, NcUnits.MILLIBAR ), t850Amt, td850Amt ); - if ( !PRLibrary.checkNullOrInvalidValue(thtlcl) ) - return new Amount ( Unit.ONE ); - /*Find parcel temperature along pseudoadiabat at 500 mb. - * The parcel temperature tp is the temperature in Celsius at 500 mb of a parcel, - * which lies on the moist adiabat determined by the sounding at 850 mb - */ - Amount tp = PRLibrary.prTmst ( thtlcl, - new Amount( p500, NcUnits.MILLIBAR ), - new Amount ( 0, Unit.ONE ) ); - - /*Subtract the parcel temperature from the temperature at 500 mb, is the parcel temperature is valid*/ - - if ( !PRLibrary.checkNullOrInvalidValue(tp) ) - return new Amount ( Unit.ONE ); - t500Amt = PRLibrary.checkAndConvertInputAmountToExpectedUnits(t500Amt, SI.KELVIN); - float t500 = t500Amt.getValue().floatValue(); - psshow = t500 - tp.getValue().floatValue(); + /** + * Computes low, middle, and high elevation Haines indices from the + * temperature and the dewpoint. + * + * @param tc1 + * - temperature ( in Celsius ) + * @param tc2 + * - temperature ( in Celsius ) + * @param dwpc + * - dewpoint ( in Celsius ) + * @param itype + * - Haines index: 1- Low 2 - Middle 3 - High + * + * @return + * @throws InvalidValueException + * @throws NullPointerException + */ + public static Amount psHans(Amount tc1Amt, Amount tc2Amt, Amount dwpcAmt, + Amount itypeAmt) { + Amount pshans = new Amount(); + if (!PRLibrary.checkNullOrInvalidValue(tc1Amt) + || !PRLibrary.checkNullOrInvalidValue(tc2Amt) + || !PRLibrary.checkNullOrInvalidValue(dwpcAmt) + || !PRLibrary.checkNullOrInvalidValue(itypeAmt)) + return new Amount(Unit.ONE); + float a = GempakConstants.RMISSD; + float b = GempakConstants.RMISSD; + float tc1 = tc1Amt.getValue().floatValue(); + float tc2 = tc2Amt.getValue().floatValue(); + float dwpc = dwpcAmt.getValue().floatValue(); + float itype = itypeAmt.getValue().floatValue(); + /* Compute the Haines index */ + if (itype == 1) { + a = ((tc2 - tc1) - 3) * (2 / 5) + 1; + b = ((tc1 - dwpc) - 5) * (2 / 5) + 1; + } else if (itype == 2) { + a = ((tc1 - tc2) - 5) * (2 / 6) + 1; + b = ((tc1 - dwpc) - 5) * (2 / 8) + 1; + } else if (itype == 3) { + a = ((tc1 - tc2) - 17) * (2 / 5) + 1; + b = ((tc1 - dwpc) - 14) * (2 / 7) + 1; + } - return new Amount ( psshow, Unit.ONE); - } - - /** - * Computes the vertical totals index - * @param t850 - 850 mb temperature ( in Celsius ) - * @param t500 - 500 mb temperature ( in Celsius ) - * @return the vertical totals index as a difference of the temperature at 850 mb and the temperature at 500 mb, if neither value is missing. - * Else it returns RMISSD (-9999) - * @throws InvalidValueException - * @throws NullPointerException - */ - public final static Amount psVtot ( Amount t850, Amount t500 ) { - if ( ! PRLibrary.checkNullOrInvalidValue(t850 ) - || ! PRLibrary.checkNullOrInvalidValue(t500 ) ) - return new Amount ( Unit.ONE ); - - return new Amount ( t850.getValue().floatValue() - t500.getValue().floatValue() , Unit.ONE ); - } - - /** - * Computes the total Totals index from the temperature and dewpoint at 850 mb and the temperature at 500 mb - * - * @param t850 - 850 mb temperature ( in Celsius ) - * @param td850 - 850 mb dewpoint ( in Celsius ) - * @param t500 - 500 mb temperature ( in Celsius ) - * @return the total totals index if none of the input parameters are missing and - * RMISSD (-9999) otherwise - * @throws InvalidValueException - * @throws NullPointerException - */ - public final static Amount psTotl ( Amount t850, Amount td850, Amount t500 ) { - float pstotl = GempakConstants.RMISSD; - - if ( !PRLibrary.checkNullOrInvalidValue(t500) - || !PRLibrary.checkNullOrInvalidValue(td850) - || !PRLibrary.checkNullOrInvalidValue(t850) ) - return new Amount ( Unit.ONE ); - - /*Compute the vertical totals*/ - Amount vtot = psVtot ( t850, t500 ); - - /*Compute the cross totals*/ - Amount ctot = psCtot ( td850, t500 ); + a = (a > 0.9f ? a : 0.9f); + a = (a < 3.1f ? a : 3.1f); + b = (b > 0.9f ? b : 0.9f); + b = (b < 3.1f ? b : 3.1f); + pshans = new Amount(a + b, Unit.ONE); + // } + return pshans; + } - if ( !PRLibrary.checkNullOrInvalidValue( vtot ) - || ! PRLibrary.checkNullOrInvalidValue( ctot ) ) - return new Amount ( Unit.ONE ); - pstotl = vtot.getValue().floatValue() + ctot .getValue().floatValue(); + /** + * + * Computes the 'K' index + * + * @param t850 + * - 850 mb temperature ( in Celsius ) + * @param t700 + * - 700 mb temperature ( in Celsius ) + * @param t500 + * - 500 mb temperature ( in Celsius ) + * @param td850 + * - 850 mb dewpoint ( in Celsius ) + * @param td700 + * - 700 mb dewpoint ( in Celsius ) + * @return returns the 'K' index if all the input values are valid and + * RMISSD ( -9999 ) otherwise + * @throws InvalidValueException + * @throws NullPointerException + */ + public final static Amount pskinx(Amount t850, Amount t700, Amount t500, + Amount td850, Amount td700) { + Amount pskinx = new Amount(); + if (!PRLibrary.checkNullOrInvalidValue(t850) + || !PRLibrary.checkNullOrInvalidValue(t700) + || !PRLibrary.checkNullOrInvalidValue(t500) + || !PRLibrary.checkNullOrInvalidValue(td850) + || !PRLibrary.checkNullOrInvalidValue(td700)) + return new Amount(Unit.ONE); + pskinx = new Amount( + (t850.getValue().floatValue() - t500.getValue().floatValue()) + + td850.getValue().floatValue() + - (t700.getValue().floatValue() - td700.getValue() + .floatValue()), Unit.ONE); - return new Amount ( pstotl, Unit.ONE ); - } - - /** - * Computes the SWEAT index. Winds must be input in m/sec - * @param t850 - 850 mb temperature - * @param td850 - 850 mb dewpoint - * @param t500 - 500 mb temperature - * @param spd850 - 850 mb windspeed ( in m/sec ) - * @param spd500 - 500 mb windspeed ( in m/sec ) - * @param dir850 - 850 mb wind direction - * @param dir500 - 500 mb wind direction - * @return the SWEAT index if none of the inputs are missing and RMISSD ( -9999 ) otherwise - * @throws InvalidValueException - * @throws NullPointerException - */ - public final static Amount psSwet ( Amount t850, Amount td850, Amount t500, - Amount spd850, Amount spd500, - Amount dir850, Amount dir500 ) { - float pssweat = GempakConstants.RMISSD; - if ( !PRLibrary.checkNullOrInvalidValue( t500 ) - || !PRLibrary.checkNullOrInvalidValue( td850 ) - || !PRLibrary.checkNullOrInvalidValue( t500 ) - || !PRLibrary.checkNullOrInvalidValue( spd850 ) - || !PRLibrary.checkNullOrInvalidValue( spd500 ) - || !PRLibrary.checkNullOrInvalidValue( dir850 ) - || !PRLibrary.checkNullOrInvalidValue( dir500 ) ) - return new Amount ( Unit.ONE ); - /* - * (Non-Javadoc): - * All computations are from - * Miller, R.C., 1972: Notes on Severe Storm Forecasting Procedures of - * the Air Force Global Weather Central, AWS Tech. Report 200 - */ - /*Convert meters per second to knots*/ - Amount skt850 = PRLibrary.checkAndConvertInputAmountToExpectedUnits(spd850, NonSI.KNOT) ; - Amount skt500 = PRLibrary.checkAndConvertInputAmountToExpectedUnits(spd500, NonSI.KNOT) ; - - /* Compute the total totals index. If < 49, set term to zero.*/ - float total = psTotl ( t850, td850, t500 ).getValue().floatValue(); - float term2 = total - 49; - if ( total < 49 ) - term2 = 0; - - /* Compute shear term.*/ - - float dif = dir500.getValue().floatValue() - dir850.getValue().floatValue(); - float s = ( float ) ( Math.sin ( dif * GempakConstants.DTR ) ); - float shear = 125 * ( s + 0.2f ); - - /*Make various wind checks.*/ - float dir850Val = dir850.getValue().floatValue(); - float dir500Val = dir500.getValue().floatValue(); - float skt500Val = skt500.getValue().floatValue(); - float skt850Val = skt850.getValue().floatValue(); - - if ((dir850Val < 130.) || (dir850Val > 250.)) - shear = 0; - if ((dir500Val < 210.) || (dir500Val > 310.)) - shear = 0; - if ((skt500Val < 15.) || (skt850Val < 15. )) - shear = 0; - if (dif <= 0) - shear = 0; - - /*Check for sub-zero dewpoint*/ - float dwp850 = td850.getValue().floatValue(); - if ( dwp850 < 0 ) - dwp850 = 0; - - /*Calculate SWEAT index*/ - pssweat = 12 * dwp850 + 20 * term2 + 2 * skt850Val + skt500Val + shear; - return new Amount ( pssweat , Unit.ONE); - } - - /** - * Finds the most unstable level of a sounding from the surface to the input pressure level. - * @param listOfNcSoundingLayer - the list of NcSoundingLayer to search - * @param plev - input pressure level - * @return the most unstable level of a sounding from the surface to the input pressure level (plev), - * if plev is not -1 and all computations fall through correctly.Else it returns an empty NcSoundingLayer object - * @throws Exception - */ - public static NcSoundingLayer2 psUstb(List listOfNcSoundingLayer, PressureLevel plev ) throws Exception{ - //TODO: update to find pressure value between 2 levels - NcSoundingLayer2 outputNcSoundingLayer = new NcSoundingLayer2(); - class PressureComparator implements Comparator < NcSoundingLayer2>{ - public int compare(NcSoundingLayer2 n1, NcSoundingLayer2 n2){ - return Float.compare(n2.getPressure().getValue().floatValue(), n1.getPressure().getValue().floatValue()); - } - } - if ( listOfNcSoundingLayer != null && listOfNcSoundingLayer.size() > 0 ){ - if ( listOfNcSoundingLayer.size() > 1 ) - Collections.sort(listOfNcSoundingLayer, new PressureComparator() ); - - NcSoundingLayer2 surfaceLevelNcSoundingLayer = listOfNcSoundingLayer.get( 0 ); - PressureLevel pressureAtSurfaceLevel = surfaceLevelNcSoundingLayer.getPressure(); - NcSoundingLayer2 topLevelNcSoundingLayer = PCLibrary.pcFtop( listOfNcSoundingLayer ); - PressureLevel pressureAtTopLevel = topLevelNcSoundingLayer.getPressure(); - float plevVal = plev.getValue().floatValue(); - float presAtSurfaceLevelValue = pressureAtSurfaceLevel.getValue().floatValue(); - float presAtTopLevelVal = pressureAtTopLevel.getValue().floatValue(); - if( plevVal > presAtSurfaceLevelValue) - return outputNcSoundingLayer; - else if ( plevVal == -1 || plevVal <= presAtTopLevelVal ) - plev = pressureAtTopLevel; - + return pskinx; + } + + /** + * Computes the Showalter index + * + * @param t850 + * - 850 mb temperature ( in Celsius ) + * @param td850 + * - 850 mb dewpoint ( in Celsius ) + * @param t500 + * - 500 mb temperature ( in Celsius ) + * @return the Showalter index if all the three input parameters are valid + * and the parcel temperature is computed correctly. Otherwise, it + * returns RMISSD (-9999). + */ + public final static Amount psShow(Amount t850Amt, Amount td850Amt, + Amount t500Amt) { + float psshow = GempakConstants.RMISSD; + + if (!PRLibrary.checkNullOrInvalidValue(t850Amt) + || !PRLibrary.checkNullOrInvalidValue(td850Amt) + || !PRLibrary.checkNullOrInvalidValue(t500Amt)) + return new Amount(Unit.ONE); + float p850 = 850; + float p500 = 500; + float guess = 0; + /* + * Find equivalent potential temperature at the LCL using 850 mb + * temperature and dewpoint. + */ + Amount thtlcl = PRLibrary.prThte(new Amount(p850, NcUnits.MILLIBAR), + t850Amt, td850Amt); + if (!PRLibrary.checkNullOrInvalidValue(thtlcl)) + return new Amount(Unit.ONE); + /* + * Find parcel temperature along pseudoadiabat at 500 mb. The parcel + * temperature tp is the temperature in Celsius at 500 mb of a parcel, + * which lies on the moist adiabat determined by the sounding at 850 mb + */ + Amount tp = PRLibrary.prTmst(thtlcl, + new Amount(p500, NcUnits.MILLIBAR), new Amount(0, Unit.ONE)); + + /* + * Subtract the parcel temperature from the temperature at 500 mb, is + * the parcel temperature is valid + */ + + if (!PRLibrary.checkNullOrInvalidValue(tp)) + return new Amount(Unit.ONE); + t500Amt = PRLibrary.checkAndConvertInputAmountToExpectedUnits(t500Amt, + SI.KELVIN); + float t500 = t500Amt.getValue().floatValue(); + psshow = t500 - tp.getValue().floatValue(); + + return new Amount(psshow, Unit.ONE); + } + + /** + * Computes the vertical totals index + * + * @param t850 + * - 850 mb temperature ( in Celsius ) + * @param t500 + * - 500 mb temperature ( in Celsius ) + * @return the vertical totals index as a difference of the temperature at + * 850 mb and the temperature at 500 mb, if neither value is + * missing. Else it returns RMISSD (-9999) + * @throws InvalidValueException + * @throws NullPointerException + */ + public final static Amount psVtot(Amount t850, Amount t500) { + if (!PRLibrary.checkNullOrInvalidValue(t850) + || !PRLibrary.checkNullOrInvalidValue(t500)) + return new Amount(Unit.ONE); + + return new Amount(t850.getValue().floatValue() + - t500.getValue().floatValue(), Unit.ONE); + } + + /** + * Computes the total Totals index from the temperature and dewpoint at 850 + * mb and the temperature at 500 mb + * + * @param t850 + * - 850 mb temperature ( in Celsius ) + * @param td850 + * - 850 mb dewpoint ( in Celsius ) + * @param t500 + * - 500 mb temperature ( in Celsius ) + * @return the total totals index if none of the input parameters are + * missing and RMISSD (-9999) otherwise + * @throws InvalidValueException + * @throws NullPointerException + */ + public final static Amount psTotl(Amount t850, Amount td850, Amount t500) { + float pstotl = GempakConstants.RMISSD; + + if (!PRLibrary.checkNullOrInvalidValue(t500) + || !PRLibrary.checkNullOrInvalidValue(td850) + || !PRLibrary.checkNullOrInvalidValue(t850)) + return new Amount(Unit.ONE); + + /* Compute the vertical totals */ + Amount vtot = psVtot(t850, t500); + + /* Compute the cross totals */ + Amount ctot = psCtot(td850, t500); + + if (!PRLibrary.checkNullOrInvalidValue(vtot) + || !PRLibrary.checkNullOrInvalidValue(ctot)) + return new Amount(Unit.ONE); + pstotl = vtot.getValue().floatValue() + ctot.getValue().floatValue(); + + return new Amount(pstotl, Unit.ONE); + } + + /** + * Computes the SWEAT index. Winds must be input in m/sec + * + * @param t850 + * - 850 mb temperature + * @param td850 + * - 850 mb dewpoint + * @param t500 + * - 500 mb temperature + * @param spd850 + * - 850 mb windspeed ( in m/sec ) + * @param spd500 + * - 500 mb windspeed ( in m/sec ) + * @param dir850 + * - 850 mb wind direction + * @param dir500 + * - 500 mb wind direction + * @return the SWEAT index if none of the inputs are missing and RMISSD ( + * -9999 ) otherwise + * @throws InvalidValueException + * @throws NullPointerException + */ + public final static Amount psSwet(Amount t850, Amount td850, Amount t500, + Amount spd850, Amount spd500, Amount dir850, Amount dir500) { + float pssweat = GempakConstants.RMISSD; + if (!PRLibrary.checkNullOrInvalidValue(t500) + || !PRLibrary.checkNullOrInvalidValue(td850) + || !PRLibrary.checkNullOrInvalidValue(t500) + || !PRLibrary.checkNullOrInvalidValue(spd850) + || !PRLibrary.checkNullOrInvalidValue(spd500) + || !PRLibrary.checkNullOrInvalidValue(dir850) + || !PRLibrary.checkNullOrInvalidValue(dir500)) + return new Amount(Unit.ONE); + /* + * (Non-Javadoc): All computations are from Miller, R.C., 1972: Notes on + * Severe Storm Forecasting Procedures of the Air Force Global Weather + * Central, AWS Tech. Report 200 + */ + /* Convert meters per second to knots */ + Amount skt850 = PRLibrary.checkAndConvertInputAmountToExpectedUnits( + spd850, NonSI.KNOT); + Amount skt500 = PRLibrary.checkAndConvertInputAmountToExpectedUnits( + spd500, NonSI.KNOT); + + /* Compute the total totals index. If < 49, set term to zero. */ + float total = psTotl(t850, td850, t500).getValue().floatValue(); + float term2 = total - 49; + if (total < 49) + term2 = 0; + + /* Compute shear term. */ + + float dif = dir500.getValue().floatValue() + - dir850.getValue().floatValue(); + float s = (float) (Math.sin(dif * GempakConstants.DTR)); + float shear = 125 * (s + 0.2f); + + /* Make various wind checks. */ + float dir850Val = dir850.getValue().floatValue(); + float dir500Val = dir500.getValue().floatValue(); + float skt500Val = skt500.getValue().floatValue(); + float skt850Val = skt850.getValue().floatValue(); + + if ((dir850Val < 130.) || (dir850Val > 250.)) + shear = 0; + if ((dir500Val < 210.) || (dir500Val > 310.)) + shear = 0; + if ((skt500Val < 15.) || (skt850Val < 15.)) + shear = 0; + if (dif <= 0) + shear = 0; + + /* Check for sub-zero dewpoint */ + float dwp850 = td850.getValue().floatValue(); + if (dwp850 < 0) + dwp850 = 0; + + /* Calculate SWEAT index */ + pssweat = 12 * dwp850 + 20 * term2 + 2 * skt850Val + skt500Val + shear; + return new Amount(pssweat, Unit.ONE); + } + + /** + * Finds the most unstable level of a sounding from the surface to the input + * pressure level. + * + * @param listOfNcSoundingLayer + * - the list of NcSoundingLayer to search + * @param plev + * - input pressure level + * @return the most unstable level of a sounding from the surface to the + * input pressure level (plev), if plev is not -1 and all + * computations fall through correctly.Else it returns an empty + * NcSoundingLayer object + * @throws Exception + */ + public static NcSoundingLayer2 psUstb( + List listOfNcSoundingLayer, PressureLevel plev) + throws Exception { + // System.out.println(" PSLibrary/psUstb !!!!!!!!! "); + // TODO: update to find pressure value between 2 levels + NcSoundingLayer2 outputNcSoundingLayer = new NcSoundingLayer2(); + class PressureComparator implements Comparator { + public int compare(NcSoundingLayer2 n1, NcSoundingLayer2 n2) { + return Float.compare(n2.getPressure().getValue().floatValue(), + n1.getPressure().getValue().floatValue()); + } + } + + // System.out.println(" PSLibrary/psUstb 1 "); + + if (listOfNcSoundingLayer != null && listOfNcSoundingLayer.size() > 0) { + if (listOfNcSoundingLayer.size() > 1) + Collections.sort(listOfNcSoundingLayer, + new PressureComparator()); + // System.out.println(" PSLibrary/psUstb 2 "); + + NcSoundingLayer2 surfaceLevelNcSoundingLayer = listOfNcSoundingLayer + .get(0); + PressureLevel pressureAtSurfaceLevel = surfaceLevelNcSoundingLayer + .getPressure(); + NcSoundingLayer2 topLevelNcSoundingLayer = PCLibrary + .pcFtop(listOfNcSoundingLayer); + PressureLevel pressureAtTopLevel = topLevelNcSoundingLayer + .getPressure(); + float plevVal = plev.getValue().floatValue(); + float presAtSurfaceLevelValue = pressureAtSurfaceLevel.getValue() + .floatValue(); + float presAtTopLevelVal = pressureAtTopLevel.getValue() + .floatValue(); + if (plevVal > presAtSurfaceLevelValue) + return outputNcSoundingLayer; + else if (plevVal == -1 || plevVal <= presAtTopLevelVal) + plev = pressureAtTopLevel; + // System.out.println(" PSLibrary/psUstb 3 "); + + int sizeOfList = listOfNcSoundingLayer.size(); + boolean done = false; + int lev = 0; + float eps = GempakConstants.RMISSD; + + while (!done && lev < sizeOfList) { + NcSoundingLayer2 currNcSoundingLayer = listOfNcSoundingLayer + .get(lev); + // System.out.println(" PSLibrary/psUstb 4 "); + + Amount pressure = new Amount(currNcSoundingLayer.getPressure() + .getValue(), NcUnits.MILLIBAR); + Amount tmpc = new Amount(currNcSoundingLayer.getTemperature() + .getValue(), SI.CELSIUS); + Amount dwpc = new Amount(currNcSoundingLayer.getDewpoint() + .getValue(), SI.CELSIUS); + if (pressure.hasValidValue() && tmpc.hasValidValue() + && dwpc.hasValidValue()) { + WetBulbPotentialTemp thwc = new WetBulbPotentialTemp(); + // System.out.println(" PSLibrary/thwc " + + // thwc.doubleValue() + // + " pressure " + pressure.doubleValue() + " tmpc " + // + tmpc.doubleValue() + " dwpc " + // + dwpc.doubleValue()); + + thwc.setValue(PRLibrary.prThwc(pressure, tmpc, dwpc)); + if (thwc.getValue().floatValue() > eps + && (pressure.getValue().floatValue() >= plevVal)) { + eps = thwc.getValue().floatValue(); + outputNcSoundingLayer.setPressure(currNcSoundingLayer + .getPressure()); + outputNcSoundingLayer + .setTemperature(currNcSoundingLayer + .getTemperature()); + outputNcSoundingLayer.setDewpoint(currNcSoundingLayer + .getDewpoint()); + outputNcSoundingLayer.setWindSpeed(currNcSoundingLayer + .getWindSpeed()); + outputNcSoundingLayer + .setWindDirection(currNcSoundingLayer + .getWindDirection()); + outputNcSoundingLayer.setWindU(currNcSoundingLayer + .getWindU()); + outputNcSoundingLayer.setWindV(currNcSoundingLayer + .getWindV()); + outputNcSoundingLayer.setGeoHeight(currNcSoundingLayer + .getGeoHeight()); + outputNcSoundingLayer.setOmega(currNcSoundingLayer + .getOmega()); + outputNcSoundingLayer + .setSpecificHumidity(currNcSoundingLayer + .getSpecificHumidity()); + // System.out.println("Outdat: " + // + outputNcSoundingLayer.getPressure().getValueAs( + // NcUnits.MILLIBAR ).floatValue() + " " + // + outputNcSoundingLayer.getTemperature().getValueAs( + // SI.CELSIUS ).floatValue() + " " + // + outputNcSoundingLayer.getDewpoint().getValueAs( + // SI.CELSIUS ).floatValue() + " " + // + outputNcSoundingLayer.getWindSpeed().getValueAs( + // SI.METERS_PER_SECOND ).floatValue() + " " + // + + // outputNcSoundingLayer.getWindDirection().getValueAs( + // NonSI.DEGREE_ANGLE ).floatValue() + " " + // + outputNcSoundingLayer.getGeoHeight().getValueAs( + // SI.METER ).floatValue() ); + } + + } + // System.out.println("Pressure value is " + + // pressure.getValue().floatValue() + " " + + // pressure.getUnit().toString() ); + // System.out.println("lev is " + lev ); + lev++; + if (pressure.hasValidValue() + && pressure.getValue().floatValue() <= plevVal) { + done = true; + break; + } + } + } + //System.out.println("PSLibrary/psUstb Outdat: " + // + outputNcSoundingLayer.getPressure().getValue().floatValue() + // + " " + // + outputNcSoundingLayer.getTemperature().getValue() + // .floatValue() + // + " " + // + outputNcSoundingLayer.getDewpoint().getValue().floatValue() + // + " " + // + outputNcSoundingLayer.getWindSpeed().getValue().floatValue() + // + " " + // + outputNcSoundingLayer.getWindDirection().getValue() + // .floatValue() + " " + // + outputNcSoundingLayer.getGeoHeight().getValue().floatValue()); + + return outputNcSoundingLayer; + } - int sizeOfList = listOfNcSoundingLayer.size(); - boolean done = false; - int lev = 0; - float eps = GempakConstants.RMISSD; - - while ( !done && lev < sizeOfList){ - NcSoundingLayer2 currNcSoundingLayer = listOfNcSoundingLayer.get( lev ); - - Amount pressure = new Amount ( currNcSoundingLayer.getPressure().getValue(), NcUnits.MILLIBAR ); - Amount tmpc = new Amount ( currNcSoundingLayer.getTemperature().getValue(), SI.CELSIUS ); - Amount dwpc = new Amount ( currNcSoundingLayer.getDewpoint().getValue(), SI.CELSIUS ); - if ( pressure.hasValidValue() && tmpc.hasValidValue() && dwpc.hasValidValue() ){ - WetBulbPotentialTemp thwc = new WetBulbPotentialTemp(); - thwc.setValue( PRLibrary.prThwc ( pressure, tmpc, dwpc ) ); - if ( thwc.getValue().floatValue() > eps && ( pressure.getValue().floatValue() >= plevVal ) ){ - eps = thwc.getValue().floatValue(); - outputNcSoundingLayer.setPressure( currNcSoundingLayer.getPressure() ); - outputNcSoundingLayer.setTemperature( currNcSoundingLayer.getTemperature() ); - outputNcSoundingLayer.setDewpoint( currNcSoundingLayer.getDewpoint() ); - outputNcSoundingLayer.setWindSpeed( currNcSoundingLayer.getWindSpeed() ); - outputNcSoundingLayer.setWindDirection( currNcSoundingLayer.getWindDirection() ); - outputNcSoundingLayer.setWindU( currNcSoundingLayer.getWindU() ); - outputNcSoundingLayer.setWindV( currNcSoundingLayer.getWindV() ); - outputNcSoundingLayer.setGeoHeight( currNcSoundingLayer.getGeoHeight() ); - outputNcSoundingLayer.setOmega( currNcSoundingLayer.getOmega() ); - outputNcSoundingLayer.setSpecificHumidity( currNcSoundingLayer.getSpecificHumidity() ); -// System.out.println("Outdat: " -// + outputNcSoundingLayer.getPressure().getValueAs( NcUnits.MILLIBAR ).floatValue() + " " -// + outputNcSoundingLayer.getTemperature().getValueAs( SI.CELSIUS ).floatValue() + " " -// + outputNcSoundingLayer.getDewpoint().getValueAs( SI.CELSIUS ).floatValue() + " " -// + outputNcSoundingLayer.getWindSpeed().getValueAs( SI.METERS_PER_SECOND ).floatValue() + " " -// + outputNcSoundingLayer.getWindDirection().getValueAs( NonSI.DEGREE_ANGLE ).floatValue() + " " -// + outputNcSoundingLayer.getGeoHeight().getValueAs( SI.METER ).floatValue() ); - } - - } -// System.out.println("Pressure value is " + pressure.getValue().floatValue() + " " + pressure.getUnit().toString() ); -// System.out.println("lev is " + lev ); - lev++; - if ( pressure.hasValidValue() && pressure.getValue().floatValue() <= plevVal ){ - done = true; - break; - } - } - } - System.out.println("From PS_USTB Outdat: " - + outputNcSoundingLayer.getPressure().getValue().floatValue() + " " - + outputNcSoundingLayer.getTemperature().getValue().floatValue() + " " - + outputNcSoundingLayer.getDewpoint().getValue().floatValue() + " " - + outputNcSoundingLayer.getWindSpeed().getValue().floatValue() + " " - + outputNcSoundingLayer.getWindDirection().getValue().floatValue() + " " - + outputNcSoundingLayer.getGeoHeight().getValue().floatValue() ); - - return outputNcSoundingLayer; - } - - } diff --git a/ncep/gov.noaa.nws.ncep.edex.common/utility/common_static/base/ncep/hold/NcInventoryDefinitions/NotUsed/QSCT.xml b/ncep/gov.noaa.nws.ncep.edex.common/utility/common_static/base/ncep/hold/NcInventoryDefinitions/NotUsed/QSCT.xml deleted file mode 100644 index e88b70b40c..0000000000 --- a/ncep/gov.noaa.nws.ncep.edex.common/utility/common_static/base/ncep/hold/NcInventoryDefinitions/NotUsed/QSCT.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - QSCT - pluginName,dataTime - - - - - - - - - diff --git a/ncep/gov.noaa.nws.ncep.edex.common/utility/edex_static/base/ncep/stns/vors.xml b/ncep/gov.noaa.nws.ncep.edex.common/utility/edex_static/base/ncep/stns/vors.xml index 80239cf1d0..7453bdf5f4 100755 --- a/ncep/gov.noaa.nws.ncep.edex.common/utility/edex_static/base/ncep/stns/vors.xml +++ b/ncep/gov.noaa.nws.ncep.edex.common/utility/edex_static/base/ncep/stns/vors.xml @@ -1,7049 +1,7097 @@ - - - - - YSJ - 000395 - ST_JOHN - NB - CN - 45.32 - -65.88 - 0 - 0 - - - - HUL - 000341 - HOULTON - ME - US - 46.04 - -67.83 - 0 - 0 - - - - PQI - 000367 - PRESQUE_ISLE - ME - US - 46.77 - -68.09 - 0 - 0 - - - - MLT - 000183 - MILLINOCKET - ME - US - 45.58 - -68.52 - 0 - 0 - - - - BGR - 000029 - BANGOR - ME - US - 44.84 - -68.87 - 0 - 0 - - - - ACK - 000005 - NANTUCKET - MA - US - 41.28 - -70.03 - 0 - 0 - - - - ENE - 000322 - KENNEBUNK - ME - US - 43.43 - -70.61 - 0 - 0 - - - - BOS - 000289 - BOSTON - MA - US - 42.36 - -70.99 - 0 - 0 - - - - YQB - 000391 - QUEBEC - QB - CN - 46.80 - -71.38 - 0 - 0 - - - - PVD - 000221 - PROVIDENCE - RI - US - 41.72 - -71.43 - 0 - 0 - - - - CON - 000062 - CONCORD - NH - US - 43.22 - -71.58 - 0 - 0 - - - - YSC - 000394 - SHERBROOKE - QB - CN - 45.43 - -71.68 - 0 - 0 - - - - HTO - 000340 - EAST_HAMPTON - NY - US - 40.92 - -72.32 - 0 - 0 - - - - MPV - 000188 - MONTPELIER - VT - US - 44.22 - -72.57 - 0 - 0 - - - - BDL - 000287 - WINSOR_LOCKS - CT - US - 41.94 - -72.69 - 0 - 0 - - - - PLB - 000365 - PLATTSBURGH - NY - US - 44.69 - -73.52 - 0 - 0 - - - - JFK - 000345 - NEW_YORK/JF_KENNEDY - NY - US - 40.63 - -73.77 - 0 - 0 - - - - ALB - 000012 - ALBANY - NY - US - 42.75 - -73.80 - 0 - 0 - - - - CYN - 000300 - COYLE - NJ - US - 39.82 - -74.43 - 0 - 0 - - - - SAX - 000376 - SPARTA - NJ - US - 41.07 - -74.54 - 0 - 0 - - - - MSS - 000353 - MASSENA - NY - US - 44.91 - -74.72 - 0 - 0 - - - - SIE - 000377 - SEA_ISLE - NJ - US - 39.10 - -74.80 - 0 - 0 - - - - HNK - 000338 - HANCOCK - NY - US - 42.06 - -75.32 - 0 - 0 - - - - SBY - 000242 - SALISBURY - MD - US - 38.35 - -75.52 - 0 - 0 - - - - YOW - 000390 - OTTAWA - ON - CN - 45.32 - -75.67 - 0 - 0 - - - - ETX - 000325 - EAST_TEXAS - PA - US - 40.58 - -75.68 - 0 - 0 - - - - ECG - 000086 - ELIZABETH_CITY - NC - US - 36.25 - -76.18 - 0 - 0 - - - - SYR - 000259 - SYRACUSE - NY - US - 43.16 - -76.20 - 0 - 0 - - - - ORF - 000203 - NORFOLK - VA - US - 36.89 - -76.20 - 0 - 0 - - - - EMI - 000320 - WESTMINSTER - MD - US - 39.50 - -76.98 - 0 - 0 - - - - HAR - 000126 - HARRISBURG - PA - US - 40.23 - -77.02 - 0 - 0 - - - - DCA - 000306 - WASHINGTON - DC - US - 38.86 - -77.04 - 0 - 0 - - - - RIC - 000229 - RICHMOND - VA - US - 37.50 - -77.32 - 0 - 0 - - - - CSN - 000299 - CASSANOVA - VA - US - 38.64 - -77.87 - 0 - 0 - - - - ILM - 000135 - WILMINGTON - NC - US - 34.35 - -77.87 - 0 - 0 - - - - SLT - 000252 - SLATE_RUN - PA - US - 41.51 - -77.97 - 0 - 0 - - - - PSB - 000368 - PHILLIPSBURG - PA - US - 40.92 - -77.99 - 0 - 0 - - - - BUF - 000044 - BUFFALO - NY - US - 42.93 - -78.65 - 0 - 0 - - - - RDU - 000372 - RALEIGH-DURHAM - NC - US - 35.87 - -78.78 - 0 - 0 - - - - JST - 000145 - JOHNSTOWN - PA - US - 40.32 - -78.83 - 0 - 0 - - - - JHW - 000346 - JAMESTOWN - NY - US - 42.19 - -79.12 - 0 - 0 - - - - LYH - 000166 - LYNCHBURG - VA - US - 37.25 - -79.23 - 0 - 0 - - - - YYZ - 000401 - TORONTO - ON - CN - 43.67 - -79.63 - 0 - 0 - - - - FLO - 000102 - FLORENCE - SC - US - 34.23 - -79.66 - 0 - 0 - - - - GSO - 000122 - GREENSBORO - NC - US - 36.05 - -79.98 - 0 - 0 - - - - CHS - 000056 - CHARLESTON - SC - US - 32.89 - -80.04 - 0 - 0 - - - - PBI - 000206 - WEST_PALM_BEACH - FL - US - 26.68 - -80.09 - 0 - 0 - - - - EKN - 000088 - ELKINS - WV - US - 38.92 - -80.10 - 0 - 0 - - - - EWC - 000326 - ELLWOOD_CITY - PA - US - 40.83 - -80.21 - 0 - 0 - - - - ERI - 000092 - ERIE - PA - US - 42.02 - -80.30 - 0 - 0 - - - - MIA - 000176 - MIAMI - FL - US - 25.80 - -80.30 - 0 - 0 - - - - VRB - 000276 - VERO_BEACH - FL - US - 27.68 - -80.49 - 0 - 0 - - - - PSK - 000369 - DUBLIN - VA - US - 37.09 - -80.71 - 0 - 0 - - - - AIR - 000280 - BELLAIRE - OH - US - 40.02 - -80.82 - 0 - 0 - - - - CLT - 000059 - CHARLOTTE - NC - US - 35.22 - -80.93 - 0 - 0 - - - - CAE - 000295 - COLUMBIA - SC - US - 33.86 - -81.05 - 0 - 0 - - - - YVV - 000396 - WIARTON - ON - CN - 44.75 - -81.10 - 0 - 0 - - - - SAV - 000239 - SAVANNAH - GA - US - 32.16 - -81.11 - 0 - 0 - - - - OMN - 000363 - ORMOND_BEACH - FL - US - 29.30 - -81.11 - 0 - 0 - - - - BKW - 000034 - BECKLEY - WV - US - 37.78 - -81.12 - 0 - 0 - - - - ORL - 000204 - ORLANDO - FL - US - 28.54 - -81.34 - 0 - 0 - - - - CRG - 000298 - JACKSONVILLE - FL - US - 30.34 - -81.51 - 0 - 0 - - - - EYW - 000096 - KEY_WEST - FL - US - 24.59 - -81.80 - 0 - 0 - - - - FMY - 000104 - FT_MEYERS - FL - US - 26.58 - -81.87 - 0 - 0 - - - - SPA - 000380 - SPARTANBURG - SC - US - 35.03 - -81.93 - 0 - 0 - - - - HNN - 000339 - HENDERSON - WV - US - 38.75 - -82.03 - 0 - 0 - - - - HMV - 000337 - HOLSTON_MOUNTAIN - TN - US - 36.44 - -82.13 - 0 - 0 - - - - CLE - 000058 - CLEVELAND - OH - US - 41.42 - -81.85 - 0 - 0 - - - - IRQ - 000344 - COLLIERS - SC - US - 33.71 - -82.16 - 0 - 0 - - - - AMG - 000015 - ALMA - GA - US - 31.54 - -82.51 - 0 - 0 - - - - SRQ - 000382 - SARASOTA - FL - US - 27.40 - -82.55 - 0 - 0 - - - - APE - 000283 - APPLETON - OH - US - 40.15 - -82.59 - 0 - 0 - - - - PIE - 000212 - SAINT_PETERSBURG - FL - US - 27.91 - -82.68 - 0 - 0 - - - - ECK - 000316 - PECK - MI - US - 43.26 - -82.72 - 0 - 0 - - - - CTY - 000066 - CROSS_CITY - FL - US - 29.60 - -83.05 - 0 - 0 - - - - ODF - 000360 - TOCCOA - GA - US - 34.70 - -83.30 - 0 - 0 - - - - DXO - 000315 - DETROIT - MI - US - 42.21 - -83.37 - 0 - 0 - - - - ASP - 000284 - OSCODA - MI - US - 44.45 - -83.39 - 0 - 0 - - - - MCN - 000170 - MACON - GA - US - 32.69 - -83.65 - 0 - 0 - - - - FNT - 000328 - FLINT - MI - US - 42.97 - -83.74 - 0 - 0 - - - - VXV - 000388 - KNOXVILLE - TN - US - 35.90 - -83.89 - 0 - 0 - - - - ROD - 000373 - ROSEWOOD - OH - US - 40.29 - -84.04 - 0 - 0 - - - - MBS - 000168 - SAGINAW - MI - US - 43.53 - -84.08 - 0 - 0 - - - - LOZ - 000160 - LONDON - KY - US - 37.03 - -84.12 - 0 - 0 - - - - ABY - 000004 - ALBANY - GA - US - 31.65 - -84.30 - 0 - 0 - - - - SSM - 000255 - SAULT_STE_MARIE - MI - US - 46.41 - -84.31 - 0 - 0 - - - - TLH - 000264 - TALLAHASSEE - FL - US - 30.56 - -84.37 - 0 - 0 - - - - ATL - 000019 - ATLANTA - GA - US - 33.63 - -84.44 - 0 - 0 - - - - CVG - 000067 - COVINGTON - KY - US - 39.02 - -84.70 - 0 - 0 - - - - GQO - 000331 - CHATTANOOGA - TN - US - 34.96 - -85.15 - 0 - 0 - - - - FWA - 000109 - FT_WAYNE - IN - US - 40.98 - -85.19 - 0 - 0 - - - - LGC - 000350 - LA_GRANGE - GA - US - 33.05 - -85.21 - 0 - 0 - - - - GRR - 000332 - GRAND_RAPIDS - MI - US - 42.79 - -85.50 - 0 - 0 - - - - TVC - 000270 - TRAVERSE_CITY - MI - US - 44.67 - -85.55 - 0 - 0 - - - - LOU - 000159 - LOUISVILLE - KY - US - 38.10 - -85.58 - 0 - 0 - - - - MKG - 000179 - MUSKEGON - MI - US - 43.17 - -86.04 - 0 - 0 - - - - PMM - 000366 - PULLMAN - MI - US - 42.47 - -86.11 - 0 - 0 - - - - GIJ - 000330 - NILES - MI - US - 41.77 - -86.32 - 0 - 0 - - - - MGM - 000175 - MONTGOMERY - AL - US - 32.22 - -86.32 - 0 - 0 - - - - IND - 000136 - INDIANAPOLIS - IN - US - 39.81 - -86.37 - 0 - 0 - - - - BWG - 000047 - BOWLING_GREEN - KY - US - 36.93 - -86.44 - 0 - 0 - - - - BNA - 000037 - NASHVILLE - TN - US - 36.14 - -86.68 - 0 - 0 - - - - CEW - 000052 - CRESTVIEW - FL - US - 30.83 - -86.68 - 0 - 0 - - - - VUZ - 000387 - VULCAN - AL - US - 33.67 - -86.90 - 0 - 0 - - - - BVT - 000293 - LAFAYETTE - IN - US - 40.56 - -87.07 - 0 - 0 - - - - TTH - 000384 - TERRE_HAUTE - IN - US - 39.49 - -87.25 - 0 - 0 - - - - MSL - 000191 - MUSCLE_SHOALS - AL - US - 34.70 - -87.48 - 0 - 0 - - - - SAW - 000189 - SAWYER - MI - US - 46.35 - -87.38 - 0 - 0 - - - - PXV - 000370 - POCKET_CITY - IN - US - 37.93 - -87.76 - 0 - 0 - - - - ORD - 000202 - O'HARE_INTERNATIONAL - IL - US - 41.98 - -87.90 - 0 - 0 - - - - GRB - 000119 - GREEN_BAY - WI - US - 44.56 - -88.19 - 0 - 0 - - - - BAE - 000285 - MILWAUKEE - WI - US - 43.12 - -88.28 - 0 - 0 - - - - JOT - 000348 - JOLIET - IL - US - 41.55 - -88.32 - 0 - 0 - - - - SJI - 000378 - SEMMNES - AL - US - 30.73 - -88.36 - 0 - 0 - - - - IGB - 000133 - BIGBEE - MS - US - 33.48 - -88.52 - 0 - 0 - - - - MEI - 000172 - MERIDIAN - MS - US - 32.38 - -88.80 - 0 - 0 - - - - DEC - 000070 - DECATUR - IL - US - 39.74 - -88.86 - 0 - 0 - - - - YQT - 000393 - THUNDER_BAY - ON - CN - 48.37 - -89.32 - 0 - 0 - - - - DYR - 000083 - DYERSBURG - TN - US - 36.02 - -89.32 - 0 - 0 - - - - RHI - 000228 - RHINELANDER - WI - US - 45.63 - -89.45 - 0 - 0 - - - - BDF - 000024 - BRADFORD - IL - US - 41.16 - -89.59 - 0 - 0 - - - - DLL - 000310 - DELLS - WI - US - 43.55 - -89.76 - 0 - 0 - - - - MEM - 000173 - MEMPHIS - TN - US - 35.06 - -89.98 - 0 - 0 - - - - LEV - 000349 - GRAND_ISLE - LA - US - 29.18 - -90.10 - 0 - 0 - - - - JAN - 000142 - JACKSON - MS - US - 32.51 - -90.17 - 0 - 0 - - - - MSY - 000195 - NEW_ORLEANS - LA - US - 30.00 - -90.27 - 0 - 0 - - - - FAM - 000097 - FARMINGTON - MO - US - 37.67 - -90.23 - 0 - 0 - - - - MCB - 000169 - MC_COMB - MS - US - 31.30 - -90.26 - 0 - 0 - - - - SQS - 000381 - SIDON - MS - US - 33.46 - -90.28 - 0 - 0 - - - - STL - 000257 - ST_LOUIS - MO - US - 38.86 - -90.48 - 0 - 0 - - - - DBQ - 000069 - DUBUQUE - IA - US - 42.40 - -90.71 - 0 - 0 - - - - ARG - 000018 - WALNUT_RIDGE - AR - US - 36.11 - -90.95 - 0 - 0 - - - - UIN - 000386 - QUINCY - IL - US - 39.85 - -91.28 - 0 - 0 - - - - BTR - 000042 - BATON_ROUGE - LA - US - 30.48 - -91.30 - 0 - 0 - - - - ODI - 000361 - NODINE - MN - US - 43.91 - -91.47 - 0 - 0 - - - - EAU - 000085 - EAU_CLAIRE - WI - US - 44.90 - -91.48 - 0 - 0 - - - - IOW - 000343 - IOWA_CITY - IA - US - 41.52 - -91.61 - 0 - 0 - - - - MLU - 000184 - MONROE - LA - US - 32.52 - -92.03 - 0 - 0 - - - - LIT - 000156 - LITTLE_ROCK - AR - US - 34.68 - -92.18 - 0 - 0 - - - - DLH - 000075 - DULUTH - MN - US - 46.80 - -92.20 - 0 - 0 - - - - COU - 000063 - COLUMBIA - MO - US - 38.82 - -92.22 - 0 - 0 - - - - AEX - 000009 - ALEXANDRIA - LA - US - 31.26 - -92.50 - 0 - 0 - - - - IRK - 000139 - KIRKSVILLE - MO - US - 40.14 - -92.59 - 0 - 0 - - - - ELD - 000319 - EL_DORADO - AR - US - 33.26 - -92.74 - 0 - 0 - - - - LCH - 000154 - LAKE_CHARLES - LA - US - 30.14 - -93.11 - 0 - 0 - - - - MSP - 000194 - MINNEAPOLIS - MN - US - 44.88 - -93.23 - 0 - 0 - - - - MCW - 000171 - MASON_CITY - IA - US - 43.09 - -93.33 - 0 - 0 - - - - SGF - 000245 - SPRINGFIELD - MO - US - 37.36 - -93.33 - 0 - 0 - - - - INL - 000137 - INTERNATIONAL_FALLS - MN - US - 48.57 - -93.40 - 0 - 0 - - - - DSM - 000079 - DES_MOINES - IA - US - 41.44 - -93.65 - 0 - 0 - - - - EIC - 000318 - SHREVEPORT - LA - US - 32.77 - -93.81 - 0 - 0 - - - - BRD - 000292 - BRAINERD - MN - US - 46.35 - -94.03 - 0 - 0 - - - - TXK - 000272 - TEXARKANA - AR - US - 33.51 - -94.07 - 0 - 0 - - - - RZC - 000374 - RAZORBACK - AR - US - 36.25 - -94.12 - 0 - 0 - - - - FSM - 000108 - FT_SMITH - AR - US - 35.38 - -94.27 - 0 - 0 - - - - FOD - 000105 - FT_DODGE - IA - US - 42.61 - -94.29 - 0 - 0 - - - - BUM - 000045 - BUTLER - MO - US - 38.27 - -94.49 - 0 - 0 - - - - MKC - 000177 - KANSAS_CITY - MO - US - 39.28 - -94.59 - 0 - 0 - - - - LFK - 000155 - LUFKIN - TX - US - 31.16 - -94.72 - 0 - 0 - - - - GGG - 000115 - LONGVIEW - TX - US - 32.42 - -94.75 - 0 - 0 - - - - BJI - 000033 - BEMIDJI - MN - US - 47.58 - -95.02 - 0 - 0 - - - - RWF - 000234 - REDWWOD_FALLS - MN - US - 44.47 - -95.13 - 0 - 0 - - - - OSW - 000205 - OSWEGO - KS - US - 37.15 - -95.20 - 0 - 0 - - - - IAH - 000131 - HOUSTON_INTERNATIONAL - TX - US - 29.96 - -95.35 - 0 - 0 - - - - OVR - 000364 - OMAHA - NE - US - 41.17 - -95.74 - 0 - 0 - - - - MLC - 000180 - MC_CALESTER - OK - US - 34.85 - -95.78 - 0 - 0 - - - - TUL - 000268 - TULSA - OK - US - 36.20 - -95.79 - 0 - 0 - - - - PWE - 000222 - PAWNEE_CITY - NE - US - 40.20 - -96.21 - 0 - 0 - - - - PSX - 000219 - PALACIOS - TX - US - 28.76 - -96.31 - 0 - 0 - - - - FSD - 000107 - SIOUX_FALLS - SD - US - 43.65 - -96.78 - 0 - 0 - - - - FAR - 000098 - FARGO - ND - US - 46.75 - -96.85 - 0 - 0 - - - - DFW - 000072 - DALLAS-FT_WORTH - TX - US - 32.87 - -97.03 - 0 - 0 - - - - ADM - 000008 - ARDMORE - OK - US - 34.21 - -97.17 - 0 - 0 - - - - GFK - 000114 - GRAND_FORKS - ND - US - 47.95 - -97.19 - 0 - 0 - - - - YWG - 000397 - WINNIPEG - MB - CN - 49.90 - -97.23 - 0 - 0 - - - - ACT - 000006 - WACO - TX - US - 31.66 - -97.27 - 0 - 0 - - - - BRO - 000041 - BROWNSVILLE - TX - US - 25.92 - -97.38 - 0 - 0 - - - - CRP - 000065 - CORPUS_CHRISTI - TX - US - 27.90 - -97.45 - 0 - 0 - - - - ICT - 000132 - WICHITA - KS - US - 37.75 - -97.58 - 0 - 0 - - - - OKC - 000198 - OKLAHOMA_CITY - OK - US - 35.36 - -97.61 - 0 - 0 - - - - SLN - 000251 - SALINA - KS - US - 38.93 - -97.62 - 0 - 0 - - - - AUS - 000020 - AUSTIN - TX - US - 30.30 - -97.70 - 0 - 0 - - - - END - 000321 - VANCE_AFB - OK - US - 36.35 - -97.92 - 0 - 0 - - - - OBH - 000358 - WOLBACH - NE - US - 41.38 - -98.35 - 0 - 0 - - - - ABR - 000003 - ABERDEEN - SD - US - 45.42 - -98.37 - 0 - 0 - - - - SAT - 000238 - SAN_ANTONIO - TX - US - 29.64 - -98.46 - 0 - 0 - - - - SPS - 000254 - WICHITA_FALLS - TX - US - 33.99 - -98.59 - 0 - 0 - - - - ONL - 000200 - ONEILL - NE - US - 42.47 - -98.69 - 0 - 0 - - - - LRD - 000161 - LAREDO - TX - US - 27.48 - -99.42 - 0 - 0 - - - - JCT - 000144 - JUNCTION - TX - US - 30.60 - -99.82 - 0 - 0 - - - - ABI - 000001 - ABILENE - TX - US - 32.48 - -99.86 - 0 - 0 - - - - GAG - 000110 - GAGE - OK - US - 36.34 - -99.88 - 0 - 0 - - - - ANW - 000282 - AINSWORTH - NE - US - 42.57 - -99.99 - 0 - 0 - - - - PIR - 000214 - PIERRE - SD - US - 44.40 - -100.17 - 0 - 0 - - - - HLC - 000335 - HILL_CITY - KS - US - 39.26 - -100.23 - 0 - 0 - - - - CDS - 000051 - CHILDRESS - TX - US - 34.37 - -100.28 - 0 - 0 - - - - SJT - 000248 - SAN_ANGELO - TX - US - 31.38 - -100.46 - 0 - 0 - - - - MCK - 000351 - MC_COOK - NE - US - 40.20 - -100.59 - 0 - 0 - - - - BIS - 000032 - BISMARK - ND - US - 46.77 - -100.67 - 0 - 0 - - - - LBF - 000152 - NORTH_PLATTE - NE - US - 41.13 - -100.72 - 0 - 0 - - - - GCK - 000112 - GARDEN_CITY - KS - US - 37.92 - -100.73 - 0 - 0 - - - - DLF - 000309 - LAUGHLIN_AFB - TX - US - 29.36 - -100.77 - 0 - 0 - - - - LBL - 000153 - LIBERAL - KS - US - 37.04 - -100.97 - 0 - 0 - - - - MOT - 000187 - MINOT - ND - US - 48.26 - -101.29 - 0 - 0 - - - - AMA - 000014 - AMARILLO - TX - US - 35.29 - -101.64 - 0 - 0 - - - - GLD - 000118 - GOODLAND - KS - US - 39.39 - -101.69 - 0 - 0 - - - - DPR - 000077 - DUPREE - SD - US - 45.08 - -101.72 - 0 - 0 - - - - LBB - 000151 - LUBBOCK_INTERNATIONAL - TX - US - 33.70 - -101.92 - 0 - 0 - - - - MAF - 000167 - MIDLAND - TX - US - 32.02 - -102.18 - 0 - 0 - - - - LAA - 000146 - LAMAR - CO - US - 38.20 - -102.69 - 0 - 0 - - - - DIK - 000074 - DICKINSIN - ND - US - 46.86 - -102.77 - 0 - 0 - - - - TXO - 000385 - TEXICO_NM/BOVINA - TX - US - 34.50 - -102.84 - 0 - 0 - - - - SNY - 000379 - SIDNEY - NE - US - 41.10 - -102.98 - 0 - 0 - - - - FST - 000329 - FT_STOCKTON - TX - US - 30.95 - -102.98 - 0 - 0 - - - - RAP - 000224 - RAPID_CITY - SD - US - 43.98 - -103.01 - 0 - 0 - - - - AKO - 000011 - AKRON - CO - US - 40.16 - -103.18 - 0 - 0 - - - - INK - 000342 - WINK - TX - US - 31.87 - -103.24 - 0 - 0 - - - - BFF - 000026 - SCOTTSBLUFF - NE - US - 41.89 - -103.48 - 0 - 0 - - - - TBE - 000261 - TOBE - CO - US - 37.27 - -103.60 - 0 - 0 - - - - TCC - 000262 - TUCUMCARI - NM - US - 35.18 - -103.60 - 0 - 0 - - - - ISN - 000140 - WILLISTON - ND - US - 48.18 - -103.63 - 0 - 0 - - - - MRF - 000190 - MARFA - TX - US - 30.30 - -103.95 - 0 - 0 - - - - PUB - 000220 - PUEBLO - CO - US - 38.29 - -104.43 - 0 - 0 - - - - ROW - 000233 - ROSWELL - NM - US - 33.34 - -104.62 - 0 - 0 - - - - DEN - 000071 - DENVER - CO - US - 39.81 - -104.66 - 0 - 0 - - - - CYS - 000301 - CHEYENNE - WY - US - 41.21 - -104.77 - 0 - 0 - - - - CIM - 000297 - CIMARRON - NM - US - 36.49 - -104.87 - 0 - 0 - - - - LVS - 000163 - LAS_VEGAS - NM - US - 35.66 - -105.14 - 0 - 0 - - - - LAR - 000148 - LARAMIE - WY - US - 41.33 - -105.72 - 0 - 0 - - - - ALS - 000013 - ALAMOSA - CO - US - 37.35 - -105.82 - 0 - 0 - - - - MLS - 000182 - MILES_CITY - MT - US - 46.38 - -105.95 - 0 - 0 - - - - DDY - 000307 - CASPER - WY - US - 43.09 - -106.28 - 0 - 0 - - - - ELP - 000090 - EL_PASO - TX - US - 31.82 - -106.28 - 0 - 0 - - - - CZI - 000302 - CRAZY_WOMAN - WY - US - 44.00 - -106.44 - 0 - 0 - - - - GGW - 000116 - GLASGOW - MT - US - 48.22 - -106.63 - 0 - 0 - - - - ABQ - 000002 - ALBUQUERQUE - NM - US - 35.04 - -106.82 - 0 - 0 - - - - DBL - 000304 - EAGLE - CO - US - 39.44 - -106.90 - 0 - 0 - - - - HBU - 000333 - GUNNISON - CO - US - 38.45 - -107.04 - 0 - 0 - - - - SHR - 000246 - SHERIDAN - WY - US - 44.84 - -107.06 - 0 - 0 - - - - TCS - 000263 - TRUTH_OR_CONSEQUENCES - NM - US - 33.28 - -107.28 - 0 - 0 - - - - CHE - 000054 - HAYDEN - CO - US - 40.52 - -107.31 - 0 - 0 - - - - DMN - 000076 - DEMING - NM - US - 32.28 - -107.60 - 0 - 0 - - - - YYN - 000400 - SWIFT_CURRENT - SA - CN - 50.28 - -107.68 - 0 - 0 - - - - FMN - 000103 - FARMINGTON - NM - US - 36.75 - -108.10 - 0 - 0 - - - - BOY - 000290 - BOYSEN_RESV. - WY - US - 43.46 - -108.30 - 0 - 0 - - - - BIL - 000031 - BILLINGS - MT - US - 45.81 - -108.63 - 0 - 0 - - - - JNC - 000347 - GRAND_JUNCTION - CO - US - 39.06 - -108.79 - 0 - 0 - - - - DVC - 000082 - DOVE_CREEK - CO - US - 37.81 - -108.93 - 0 - 0 - - - - OCS - 000359 - ROCKSPRINGS - WY - US - 41.59 - -109.02 - 0 - 0 - - - - SJN - 000247 - ST_JOHNS - AZ - US - 34.42 - -109.14 - 0 - 0 - - - - SSO - 000256 - SAN_SIMON - AZ - US - 32.27 - -109.26 - 0 - 0 - - - - LWT - 000165 - LEWISTOWN - MT - US - 47.05 - -109.61 - 0 - 0 - - - - HVR - 000129 - HAVRE - MT - US - 48.54 - -109.77 - 0 - 0 - - - - BPI - 000291 - BIG_PINEY - WY - US - 42.58 - -110.11 - 0 - 0 - - - - MTU - 000196 - MYTON - UT - US - 40.15 - -110.13 - 0 - 0 - - - - HVE - 000128 - HANKSVILLE - UT - US - 38.42 - -110.70 - 0 - 0 - - - - YXH - 000399 - MEDICINE_HAT - AB - CN - 50.02 - -110.72 - 0 - 0 - - - - JAC - 000141 - JACKSON - WY - US - 43.62 - -110.73 - 0 - 0 - - - - INW - 000138 - WINSLOW - AZ - US - 35.06 - -110.80 - 0 - 0 - - - - TUS - 000269 - TUCSON - AZ - US - 32.10 - -110.92 - 0 - 0 - - - - TBC - 000260 - TUBA_CITY - AZ - US - 36.12 - -111.27 - 0 - 0 - - - - GTF - 000123 - GREAT_FALLS - MT - US - 47.45 - -111.41 - 0 - 0 - - - - HLN - 000336 - HELENA - MT - US - 46.61 - -111.95 - 0 - 0 - - - - PHX - 000211 - PHOENIX - AZ - US - 33.43 - -112.02 - 0 - 0 - - - - SLC - 000249 - SALT_LAKE_CITY - UT - US - 40.85 - -111.98 - 0 - 0 - - - - DBS - 000305 - DUBOIS - ID - US - 44.09 - -112.21 - 0 - 0 - - - - BCE - 000023 - BRYCE_CANYON - UT - US - 37.69 - -112.30 - 0 - 0 - - - - MLD - 000352 - MALAD_CITY - ID - US - 42.20 - -112.45 - 0 - 0 - - - - DRK - 000313 - PRESCOTT - AZ - US - 34.70 - -112.48 - 0 - 0 - - - - DTA - 000080 - DELTA - UT - US - 39.30 - -112.51 - 0 - 0 - - - - DLN - 000311 - DILLON - MT - US - 45.25 - -112.55 - 0 - 0 - - - - PIH - 000213 - POCATELLO - ID - US - 42.87 - -112.65 - 0 - 0 - - - - YQL - 000392 - LETHBRIDGE - AB - CN - 49.63 - -112.80 - 0 - 0 - - - - PGS - 000210 - PEACH_SPRINGS - AZ - US - 35.62 - -113.54 - 0 - 0 - - - - BVL - 000046 - BOONEVILLE - UT - US - 40.73 - -113.76 - 0 - 0 - - - - LKT - 000157 - SALMON - ID - US - 45.02 - -114.08 - 0 - 0 - - - - FCA - 000100 - KALISPELL - MT - US - 48.21 - -114.18 - 0 - 0 - - - - ILC - 000134 - WILSON_CREEK - NV - US - 38.25 - -114.39 - 0 - 0 - - - - EED - 000087 - NEEDLES - CA - US - 34.77 - -114.47 - 0 - 0 - - - - TWF - 000271 - TWIN_FALLS - ID - US - 42.48 - -114.49 - 0 - 0 - - - - BZA - 000294 - YUMA - AZ - US - 32.77 - -114.60 - 0 - 0 - - - - ELY - 000091 - ELY - NV - US - 39.30 - -114.85 - 0 - 0 - - - - LAS - 000149 - LAS_VEGAS - NV - US - 36.08 - -115.16 - 0 - 0 - - - - MLP - 000181 - MULLAN_PASS - ID - US - 47.46 - -115.65 - 0 - 0 - - - - YXC - 000398 - CRANBROOK - BC - CN - 49.60 - -115.78 - 0 - 0 - - - - TRM - 000383 - THERMAL - CA - US - 33.63 - -116.16 - 0 - 0 - - - - BOI - 000039 - BOISE - ID - US - 43.55 - -116.19 - 0 - 0 - - - - DNJ - 000312 - MC_CALL - ID - US - 44.77 - -116.21 - 0 - 0 - - - - HEC - 000334 - HECTOR - CA - US - 34.80 - -116.46 - 0 - 0 - - - - BTY - 000043 - BEATTY - NV - US - 36.80 - -116.75 - 0 - 0 - - - - BAM - 000286 - BATTLE_MOUNTAIN - NV - US - 40.57 - -116.92 - 0 - 0 - - - - MZB - 000354 - MISSION_BAY - CA - US - 32.78 - -117.23 - 0 - 0 - - - - GEG - 000113 - SPOKANE - WA - US - 47.56 - -117.63 - 0 - 0 - - - - OAL - 000357 - COALDALE - NV - US - 38.00 - -117.77 - 0 - 0 - - - - BKE - 000288 - BAKER - OR - US - 44.84 - -117.81 - 0 - 0 - - - - REO - 000227 - ROME - OR - US - 42.59 - -117.87 - 0 - 0 - - - - LAX - 000150 - LOS_ANGELES_INTL - CA - US - 33.93 - -118.43 - 0 - 0 - - - - PDT - 000207 - PENDLETON - OR - US - 45.70 - -118.94 - 0 - 0 - - - - EHF - 000317 - BAKERSFIELD - CA - US - 35.48 - -119.10 - 0 - 0 - - - - EPH - 000324 - EPHRATA - WA - US - 47.38 - -119.42 - 0 - 0 - - - - FMG - 000327 - RENO - NV - US - 39.53 - -119.66 - 0 - 0 - - - - RZS - 000375 - SANTA_BARBARA - CA - US - 34.51 - -119.77 - 0 - 0 - - - - CZQ - 000303 - FRESNO - CA - US - 36.88 - -119.82 - 0 - 0 - - - - YKM - 000279 - YAKIMA - WA - US - 46.57 - -120.45 - 0 - 0 - - - - LKV - 000158 - LAKEVIEW - OR - US - 42.49 - -120.51 - 0 - 0 - - - - YDC - 000389 - PRINCETON - BC - CN - 49.47 - -120.52 - 0 - 0 - - - - MOD - 000186 - MODESTO - CA - US - 37.63 - -120.96 - 0 - 0 - - - - DSD - 000314 - REDMOND - WA - US - 44.25 - -121.30 - 0 - 0 - - - - SAC - 000236 - SACRAMENTO - CA - US - 38.44 - -121.55 - 0 - 0 - - - - SNS - 000253 - SALINAS - CA - US - 36.66 - -121.60 - 0 - 0 - - - - OAK - 000356 - OAKLAND - CA - US - 37.73 - -122.22 - 0 - 0 - - - - RBL - 000225 - RED_BLUFF - CA - US - 40.10 - -122.24 - 0 - 0 - - - - SEA - 000243 - SEATTLE - WA - US - 47.44 - -122.31 - 0 - 0 - - - - BLI - 000035 - BELLINGHAM - WA - US - 48.95 - -122.58 - 0 - 0 - - - - PDX - 000208 - PORTLAND - OR - US - 45.58 - -122.60 - 0 - 0 - - - - PYE - 000371 - POINT_REYES - CA - US - 38.08 - -122.87 - 0 - 0 - - - - OED - 000362 - MEDFORD - OR - US - 42.48 - -122.91 - 0 - 0 - - - - EUG - 000093 - EUGENE - OR - US - 44.12 - -123.22 - 0 - 0 - - - - ENI - 000323 - UKIAH - CA - US - 39.05 - -123.27 - 0 - 0 - - - - ONP - 000201 - NEWPORT - OR - US - 44.58 - -124.06 - 0 - 0 - - - - HQM - 000127 - HOQUIAM - WA - US - 46.95 - -124.15 - 0 - 0 - - - - FOT - 000106 - FORTUNA - CA - US - 40.67 - -124.23 - 0 - 0 - - - - TOU - 000265 - NEAH_BAY - WA - US - 48.30 - -124.63 - 0 - 0 - - - - YQV - 000402 - YORKTON - SA - CN - 51.27 - -102.47 - 0 - 0 - - - - ANN - 0 - ANNETTE_ISLAND - AK - US - 55.05 - -131.57 - 0 - 0 - - - - LVD - 0 - LEVEL_ISLAND - AK - US - 56.47 - -133.08 - 0 - 0 - - - - BKA - 0 - BIORKA_ISLAND - AK - US - 56.86 - -135.55 - 0 - 0 - - - - SSR - 0 - SISTERS_ISLAND - AK - US - 58.17 - -135.25 - 0 - 0 - - - - JNU - 0 - JUNEAU - AK - US - 58.35 - -134.58 - 0 - 0 - - - - YAK - 0 - YAKUTAT - AK - US - 59.50 - -139.67 - 0 - 0 - - - - MDO - 0 - MIDDLETON_ISLAND - AK - US - 59.45 - -146.30 - 0 - 0 - - - - JOH - 0 - JOHNSTONE_POINT - AK - US - 60.48 - -146.60 - 0 - 0 - - - - ODK - 0 - KODIAK - AK - US - 57.75 - -152.50 - 0 - 0 - - - - HOM - 0 - HOMER - AK - US - 59.65 - -151.48 - 0 - 0 - - - - ENA - 0 - KENAI - AK - US - 60.57 - -151.25 - 0 - 0 - - - - ANC - 0 - ANCHORAGE - AK - US - 61.17 - -150.00 - 0 - 0 - - - - BGQ - 0 - BIG_LAKE - AK - US - 61.53 - -149.82 - 0 - 0 - - - - ORT - 0 - NORTHWAY - AK - US - 62.97 - -141.93 - 0 - 0 - - - - GKN - 0 - GULKANA - AK - US - 62.15 - -145.45 - 0 - 0 - - - - TKA - 0 - TALKEETNA - AK - US - 62.32 - -150.10 - 0 - 0 - - - - SQA - 0 - SPARREVOHN - AK - US - 61.10 - -155.63 - 0 - 0 - - - - DLG - 0 - DILLINGHAM - AK - US - 59.05 - -158.50 - 0 - 0 - - - - AKN - 0 - KING_SALMON - AK - US - 58.68 - -156.65 - 0 - 0 - - - - PDN - 0 - PORT_HEIDEN - AK - US - 56.95 - -158.65 - 0 - 0 - - - - CDB - 0 - COLD_BAY - AK - US - 55.20 - -162.73 - 0 - 0 - - - - DUT - 0 - DUTCH_HARBOR - AK - US - 53.90 - -166.55 - 0 - 0 - - - - NUD - 0 - ADAK - AK - US - 51.88 - -176.65 - 0 - 0 - - - - SYA - 0 - SHEMYA - AK - US - 52.72 - 174.12 - 0 - 0 - - - - SPY - 0 - ST_PAUL_ISLAND - AK - US - 57.17 - -170.22 - 0 - 0 - - - - EHM - 0 - CAPE_NEWENHAM - AK - US - 58.66 - -162.07 - 0 - 0 - - - - HPB - 0 - HOOPER_BAY - AK - US - 61.52 - -166.14 - 0 - 0 - - - - BET - 0 - BETHEL - AK - US - 60.78 - -161.83 - 0 - 0 - - - - ANI - 0 - ANIAK - AK - US - 61.59 - -159.61 - 0 - 0 - - - - SMA - 0 - ST_MARYS - AK - US - 62.06 - -163.30 - 0 - 0 - - - - UNK - 0 - UNALAKLEET - AK - US - 63.88 - -160.80 - 0 - 0 - - - - ULL - 0 - KUKULIAK - AK - US - 63.70 - -170.48 - 0 - 0 - - - - MCG - 0 - MC_GRATH - AK - US - 62.95 - -155.60 - 0 - 0 - - - - ENN - 0 - NENANA - AK - US - 64.55 - -149.07 - 0 - 0 - - - - FAI - 0 - FAIRBANKS - AK - US - 64.82 - -147.85 - 0 - 0 - - - - BIG - 0 - BIG_DELTA - AK - US - 64.00 - -145.72 - 0 - 0 - - - - FYU - 0 - FORT_YUKON - AK - US - 66.57 - -145.25 - 0 - 0 - - - - BTT - 0 - BETTLES - AK - US - 66.92 - -151.53 - 0 - 0 - - - - TAL - 0 - TANANA - AK - US - 65.18 - -152.18 - 0 - 0 - - - - CQR - 0 - CHANDALAR_LAKE - AK - US - 67.50 - -148.47 - 0 - 0 - - - - SCC - 0 - DEADHORSE - AK - US - 70.20 - -148.47 - 0 - 0 - - - - BTI - 0 - BARTER_ISLAND - AK - US - 70.13 - -143.57 - 0 - 0 - - - - BRW - 0 - BARROW - AK - US - 71.28 - -156.77 - 0 - 0 - - - - GAL - 0 - GALENA - AK - US - 64.73 - -156.93 - 0 - 0 - - - - OME - 0 - NOME - AK - US - 64.52 - -165.45 - 0 - 0 - - - - OTZ - 0 - KOTZEBUE - AK - US - 66.88 - -162.60 - 0 - 0 - - - - WLK - 0 - SELAWIK - AK - US - 66.60 - -160.00 - 0 - 0 - - - - HSL - 0 - HUSLIA - AK - US - 65.71 - -156.37 - 0 - 0 - - - - BSF - 0 - BRADSHAW - HI - US - 19.76 - -155.39 - 0 - 0 - - - - UPP - 0 - UPOLU_POINT - HI - US - 20.20 - -155.84 - 0 - 0 - - - - ITO - 0 - HILO - HI - US - 19.72 - -155.01 - 0 - 0 - - - - HNL - 0 - HONOLULU - HI - US - 21.33 - -157.93 - 0 - 0 - - - - OGG - 0 - MAUI - HI - US - 20.91 - -156.42 - 0 - 0 - - - - NDB - 0 - VALLEY_ISLAND - HI - US - 20.88 - -156.44 - 0 - 0 - - - - MUE - 0 - KAMUELA - HI - US - 20.00 - -155.67 - 0 - 0 - - - - NGF - 0 - KANEOHE_BAY - HI - US - 21.45 - -157.76 - 0 - 0 - - - - MKK - 0 - MOLOKAI - HI - US - 21.14 - -157.17 - 0 - 0 - - - - NBS - 0 - BARKING_SANDS - HI - US - 22.04 - -159.79 - 0 - 0 - - - - CKH - 0 - KOKO_HEAD - HI - US - 21.27 - -157.70 - 0 - 0 - - - - IAI - 0 - KONA - HI - US - 19.65 - -156.02 - 0 - 0 - - - - LLD - 0 - LANAI - HI - US - 20.77 - -156.97 - 0 - 0 - - - - LNY - 0 - LANAI_CITY - HI - US - 20.76 - -156.97 - 0 - 0 - - - - LIH - 0 - LIHUE - HI - US - 21.97 - -159.34 - 0 - 0 - - - - SOK - 0 - SOUTH_KAUAI - HI - US - 21.90 - -159.53 - 0 - 0 - - - - RSW - 0 - LEE_COUNTY - FL - US - 26.53 - -81.78 - 0 - 0 - - - - PZD - 0 - PECAN - GA - US - 31.66 - -84.29 - 0 - 0 - - - - IIU - 0 - LOUISVILLE - KY - US - 38.10 - -85.58 - 0 - 0 - - - - HRV - 0 - HARVEY - LA - US - 29.85 - -90.00 - 0 - 0 - - - - MCI - 0 - KANSAS_CITY - MO - US - 39.29 - -94.74 - 0 - 0 - - - - TTT - 0 - MAVERICK - TX - US - 32.87 - -97.04 - 0 - 0 - - - - CWK - 0 - CENTEX - TX - US - 30.38 - -97.53 - 0 - 0 - - - - CME - 0 - CHISUM - NM - US - 33.34 - -104.62 - 0 - 0 - - - - FTI - 0 - FT_UNION - NM - US - 35.66 - -105.14 - 0 - 0 - - - - RSK - 0 - RATTLESNAKE - NM - US - 36.75 - -108.10 - 0 - 0 - - - - HUH - 0 - WHATCOM - WA - US - 48.95 - -122.58 - 0 - 0 - - - - ASRF - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - AYPY - 940350 - PORT_MORESBY_INTL - - NG - -9.43 - 147.22 - 47 - 0 - - - - BGSF - 042310 - SONDRE_STROMFJORD - - GL - 67.00 - -50.80 - 53 - 0 - - - - BIRK - 040300 - REYKJAVIK - - IL - 64.13 - -21.90 - 61 - 0 - - - - CWEG - 999999 - ALBERTA_WEATHER_CENTRE - - CN - 53.50 - -113.50 - -9999 - 0 - - - - CWLW - 712030 - KELOWNA - BC - CN - 49.95 - -119.40 - 456 - 0 - - - - CWNT - 712500 - TURTLE_MOUNTAIN - AB - CN - 49.58 - -114.42 - 2164 - 0 - - - - CWTO - 716380 - TORONTO_A_E_S__HQ - ON - CN - 43.78 - -79.47 - 187 - 0 - - - - CWUL - 999999 - QUEBEC_FCST_OFFICE - - CN - 45.50 - -73.68 - -9999 - 0 - - - - CYQX - 718030 - GANDER_INTL_AIRPORT - NF - CN - 48.95 - -54.57 - 151 - 0 - - - - DTTA - 607150 - TUNIS/CARTHAGE - - TS - 36.83 - 10.23 - 4 - 0 - - - - EBBR - 064510 - BRUSSELS_NATIONAL - - BX - 50.90 - 4.53 - 58 - 0 - - - - EDMM - 108680 - MUENCHEN - - DL - 48.25 - 11.58 - 484 - 0 - - - - EDZB - 102380 - BERGEN/HOHNE - - DL - 52.82 - 9.93 - 70 - 0 - - - - EDZE - 104100 - ESSEN/MULHEIM - - DL - 51.40 - 6.97 - 161 - 0 - - - - EDZF - 106370 - FRANKFURT/MAIN - - DL - 50.05 - 8.58 - 112 - 0 - - - - EDZH - 107710 - GAERMERSDORF - - DL - 49.43 - 11.90 - 419 - 0 - - - - EDZM - 108680 - MUENCHEN - - DL - 48.25 - 11.58 - 484 - 0 - - - - EETN - 260380 - TALLIN - - BY - 59.35 - 24.80 - 44 - 0 - - - - EFHK - 029740 - HELSINKI/VANTAA - - FI - 60.32 - 24.97 - 56 - 0 - - - - EFRO - 028450 - ROVANIEMI(CIV/MIL) - - FI - 66.57 - 25.83 - 201 - 0 - - - - EGJJ - 038950 - JERSEY_AIRPORT - - UK - 49.22 - -2.20 - 84 - 0 - - - - EHAM - 062400 - AMSTERDAM/SCHIPHOL - - NL - 52.30 - 4.77 - -2 - 0 - - - - EHDB - 062600 - DE_BILT - - NL - 52.10 - 5.18 - 4 - 0 - - - - EINN - 039620 - SHANNON_AIRPORT - - IE - 52.70 - -8.92 - 20 - 0 - - - - EKCH - 061800 - COPENHAGEN/KASTRUP - - DN - 55.63 - 12.67 - 5 - 0 - - - - ENMI - 999999 - OSLO - - NO - 59.50 - 10.70 - -9999 - 0 - - - - ENVN - 011520 - BODO - - NO - 67.25 - 14.40 - 8 - 0 - - - - ENVV - 014150 - STAVANGER - - NO - 58.87 - 5.67 - 34 - 0 - - - - EPWA - 123750 - WARSAW/OKECIE - - PL - 52.17 - 20.97 - 107 - 0 - - - - ESNN - 023660 - SUNDSVALL/HARNOSAND - - SN - 62.53 - 17.45 - 10 - 0 - - - - ESSA - 024600 - STOCKHOLM/ARLANDA - - SN - 59.65 - 17.95 - 61 - 0 - - - - EVRA - 999999 - RIGA_AIRPORT - LE - BY - 56.92 - 23.97 - 10 - 0 - - - - EYVI - 267300 - VILNIUS_INTL - MI - BY - 54.63 - 25.28 - 156 - 0 - - - - FAJS - 683680 - JAN_SMUTS - - ZA - -26.13 - 28.23 - 1700 - 0 - - - - FCBB - 644500 - BRAZZAVILLE/MAYA-MA - - CG - -4.25 - 15.25 - 316 - 0 - - - - FTTJ - 647000 - NDJAMENA(CIV/MIL) - - CD - 12.13 - 15.03 - 295 - 0 - - - - GCGC - 999999 - CANARY_ISLANDS - - CR - 28.50 - -16.00 - -9999 - 0 - - - - GMMC - 601550 - CASABLANCA - - MC - 33.57 - -7.67 - 62 - 0 - - - - HECA - 623660 - CAIRO_INTL_AIRPORT - - EG - 30.13 - 31.40 - 74 - 0 - - - - LBSF - 156140 - SOFIA - - BU - 42.65 - 23.38 - 595 - 0 - - - - LBWN - 155520 - VARNA - - BU - 43.20 - 27.92 - 43 - 0 - - - - LCLK - 176090 - LARNACA/LARNAX_ARPT - - CY - 34.88 - 33.63 - 2 - 0 - - - - LDZA - 131310 - ZAGREB/PLESO - - RH - 45.73 - 16.07 - 107 - 0 - - - - LDZO - 999999 - ZAGREB/PLESO - - RH - 45.73 - 16.07 - 107 - 0 - - - - LECB - 081810 - BARCELONA - - SP - 41.28 - 2.07 - 6 - 0 - - - - LEMM - 999999 - MADRID_CNM - - SP - 40.12 - -3.53 - -9999 - 0 - - - - LFBD - 075100 - BORDEAUX/MERIGNAC - - FR - 44.83 - -.70 - 61 - 0 - - - - LFMM - 076500 - MARSEILLE - - FR - 43.45 - 5.22 - 20 - 0 - - - - LFPW - 999999 - PARIS_MET_CENTER - - FR - 48.83 - 2.33 - 75 - 0 - - - - LFRN - 071300 - RENNES/ST.JACQUES - - FR - 48.07 - -1.73 - 37 - 0 - - - - LFST - 071900 - STRASBOURG/ENTZHEIM - - FR - 48.55 - 7.63 - 154 - 0 - - - - LGAT - 167160 - ATHENS/HELLENKION - - GR - 37.90 - 23.73 - 15 - 0 - - - - LHBP - 128390 - BUDAPEST/FERIHEGY - - HU - 47.43 - 19.27 - 185 - 0 - - - - LIMM - 160800 - MILANO/LINATE - - IY - 45.43 - 9.27 - 103 - 0 - - - - LJLJ - 130140 - LJUBLJANA/BRNIK - - LJ - 46.22 - 14.48 - 385 - 0 - - - - LKPR - 115180 - PRAGUE/RUZYNE - - CZ - 50.10 - 14.28 - 365 - 0 - - - - LLBG - 401800 - BEN-GURION(CIV/MIL) - - IS - 32.00 - 34.90 - 49 - 0 - - - - LMML - 165970 - LUQA/MALTA - - ML - 35.85 - 14.48 - 91 - 0 - - - - LOWW - 110360 - VIENNA/SCHWECHAT - - OS - 48.12 - 16.57 - 190 - 0 - - - - LPPT - 085360 - LISBON/PORTELA - - PO - 38.78 - -9.13 - 123 - 0 - - - - LROM - 154210 - BUCHAREST/OTOPENI - - RO - 44.55 - 26.10 - 95 - 0 - - - - LROP - 154210 - BUCHAREST/OTOPENI - - RO - 44.55 - 26.10 - 95 - 0 - - - - LSZH - 066700 - ZURICH-KLOTEN_(AUT) - - SW - 47.48 - 8.53 - 432 - 0 - - - - LTAC - 171280 - ANKARA/ESENBOGA - - TU - 40.11 - 32.97 - 949 - 0 - - - - LTBA - 170600 - ISTANBUL/ATATURK_AB - - TU - 40.97 - 28.82 - 37 - 0 - - - - LUKK - 338387 - KISHINAU - - UR - 46.93 - 28.93 - 122 - 0 - - - - LWSK - 135860 - SKOPJE/PETROVAC - - MK - 41.97 - 21.65 - 239 - 0 - - - - LYBE - 132720 - BELGRADE/SURCIN - - YG - 44.82 - 20.28 - 99 - 0 - - - - LZIB - 118160 - BRATISLAVA_IVANKA - - CZ - 48.20 - 17.20 - 130 - 0 - - - - NFFN - 916800 - NANDI/NADI_INTL - - FJ - -17.75 - 177.45 - 18 - 0 - - - - NZDT - 999999 - NEW_ZEALAND - - NZ - -41.00 - 172.50 - -9999 - 0 - - - - NZKL - 999999 - AUCKLAND - - NZ - -37.02 - 174.80 - 6 - 0 - - - - MHTG - 787200 - TEGUCIGALPA/TONCONT - - HO - 14.05 - -87.22 - 994 - 0 - - - - MPTO - 787920 - TOCUMEN/GEN._OMAR - - PM - 9.05 - -79.37 - 11 - 0 - - - - OBBB - 999999 - BAHRAIN_INTL_ARPT - - BN - 26.27 - 50.65 - 2 - 0 - - - - OBBI - 411500 - BAHRAIN_INTL_ARPT - - BN - 26.27 - 50.65 - 2 - 0 - - - - OEJD - 999999 - JEDDAH - - SD - 21.30 - 39.20 - -9999 - 0 - - - - OEJN - 410240 - JEDDAH/KING_ABD - - SD - 21.67 - 39.15 - 12 - 0 - - - - OIII - 407540 - TEHRAN/MEHRABAD_AFB - - IR - 35.68 - 51.35 - 1191 - 0 - - - - OIIX - 999999 - TEHRAN - - IR - 35.68 - 51.35 - 1191 - 0 - - - - OLBA - 401000 - BEIRUT_(CIV/MIL) - - LB - 33.82 - 35.48 - 19 - 0 - - - - OPKC - 417800 - KARACHI_INTL_ARPT - - PK - 24.90 - 67.13 - 22 - 0 - - - - OPLA - 416410 - LAHORE(CIV/MIL) - - PK - 31.52 - 74.40 - 217 - 0 - - - - OYSN - 413440 - SANA'A - - YE - 15.52 - 44.18 - 2190 - 0 - - - - PAFA - 702610 - FAIRBANKS_INTL_ARPT_(ASOS) - AK - US - 64.82 - -147.87 - 138 - 0 - - - - PAJN - 703810 - JUNEAU_INTL_AIRPORT_(ASOS) - AK - US - 58.37 - -134.58 - 7 - 0 - - - - PANC - 702730 - ANCHORAGE_INTL_ARPT_(ASOS) - AK - US - 61.17 - -150.02 - 40 - 0 - - - - RKSI - 470699 - CHAJANG_NI_(K-ARMY) - - KO - 37.87 - 127.18 - 100 - 0 - - - - RCTP - 466860 - TAIPEI/CHIANG_KAI_SHEK - - TW - 25.08 - 121.22 - 33 - 0 - - - - SABE - 875820 - AEROPARQUE(CIV/MIL) - - AG - -34.57 - -58.42 - 6 - 0 - - - - SACO - 873440 - CORDOBA_AIRPORT - - AG - -31.32 - -64.22 - 474 - 0 - - - - SAEZ - 875760 - BUENOS_AIRES/EZEIZA - - AG - -34.82 - -58.53 - 20 - 0 - - - - SAME - 874180 - MENDOZA/EL_PLUMERIL - - AG - -32.83 - -68.78 - 704 - 0 - - - - SARE - 871550 - RESISTENCIA_AIRPORT - - AG - -27.45 - -59.05 - 52 - 0 - - - - SBBE - 821930 - BELEM/VAL_DE_CAES - - BZ - -1.38 - -48.48 - 16 - 0 - - - - SBBR - 833780 - BRASILIA_(CIV/MIL) - - BZ - -15.87 - -47.93 - 1061 - 0 - - - - SBBS - 833780 - BRASILIA - - BZ - -15.87 - -47.93 - 1061 - 0 - - - - SBCT - 838400 - CURITIBA/AFONSO_PEN - - BZ - -25.52 - -49.17 - 908 - 0 - - - - SBCW - 838400 - CURITIBA/AFONSO_PEN - - BZ - -25.52 - -49.17 - 908 - 0 - - - - SBEG - 821110 - EDUARDO_GOMES_INTL - - BZ - -3.03 - -60.05 - 2 - 0 - - - - SBGL - 837460 - GALEAO/RIO(CIV/MIL) - - BZ - -22.82 - -43.25 - 6 - 0 - - - - SBGR - 837753 - GUARULHOS_(CIV/MIL) - - BZ - -23.43 - -46.47 - 750 - 0 - - - - SBRF - 828990 - RECIFE/GUARARAPES - - BZ - -8.07 - -34.85 - 19 - 0 - - - - SCCI - 859340 - PUNTA_ARENAS/PRES_C - - CH - -53.00 - -70.85 - 37 - 0 - - - - SCEL - 855740 - PUDAHUEL/ARTURO_MER - - CH - -33.38 - -70.78 - 476 - 0 - - - - SCFA - 854420 - ANTOFAGASTA/CERRO - - CH - -23.43 - -70.43 - 120 - 0 - - - - SCTE - 857990 - PUERTO_MONTT/TEPUAL - - CH - -41.42 - -73.08 - 86 - 0 - - - - SLLP - 852010 - LA_PAZ/JFK_INTL - - BO - -16.52 - -68.18 - 4014 - 0 - - - - SOCA - 814050 - CAYENNE/ROCHAMBEAU - - FG - 4.83 - -52.37 - 9 - 0 - - - - SPIM - 846280 - LIMA/JORGE_CHAVEZ - - PR - -12.00 - -77.12 - 13 - 0 - - - - TNCC - 789880 - HATO_ARPT_(CIV/MIL) - - NU - 12.20 - -68.97 - 67 - 0 - - - - TTPP - 789700 - PIARCO_INTL_AIRPORT - - TD - 10.62 - -61.35 - 15 - 0 - - - - UAAA - 368700 - ALMA-ATA - AL - RA - 43.23 - 76.93 - 847 - 0 - - - - UAFM - 835300 - FRUNZE - - RA - 42.85 - 74.53 - 760 - 0 - - - - UATT - 352290 - AKTJUBINSK - AL - KZ - 50.30 - 57.23 - 227 - 0 - - - - UBBB - 378640 - BAKU/BINE_ARPT - TB - AJ - 40.45 - 50.07 - -1 - 0 - - - - UGEE - 377890 - YEREVAN/ZAPADNY - TB - RS - 40.13 - 44.47 - 907 - 0 - - - - UGGG - 375490 - TBILISI/NOVO-AL - TB - RS - 41.68 - 44.95 - 490 - 0 - - - - UHBB - 315100 - BLAGOVESHCHENSK - HA - RA - 50.27 - 127.50 - 137 - 0 - - - - UHHH - 317350 - KHABAROVSK/NOVY - HA - RA - 48.52 - 135.16 - 72 - 0 - - - - UHNN - 999999 - NIKOLAEVSK-NA-AMURE_CENTER - HA - RA - 53.15 - 140.70 - 68 - 0 - - - - UHWW - 319600 - VLADIVOSTOK - HA - RA - 43.12 - 131.94 - 184 - 0 - - - - UHPP - 325400 - PETROPAVLOVSK-KAMCA - HA - RA - 52.97 - 158.75 - 24 - 0 - - - - UHSS - 321500 - JUZNO-SAHALINSK - HA - RA - 46.92 - 142.73 - 31 - 0 - - - - UIBB - 303090 - BRATSK - - RA - 56.07 - 101.83 - 489 - 0 - - - - UIII - 307100 - IRKUTSK - IR - RA - 52.27 - 104.35 - 513 - 0 - - - - UIKK - 302300 - KIRENSK - IR - RA - 57.77 - 108.07 - 258 - 0 - - - - UKBB - 333470 - BORISPOL'/KIEV - KV - UR - 50.33 - 30.97 - 119 - 0 - - - - UKFF - 339460 - SIMFEROPOL - - UR - 44.68 - 34.13 - 180 - 0 - - - - UKHH - 343000 - KHARKIV - KI - UR - 49.96 - 36.13 - 1550 - 0 - - - - UKLL - 333930 - LVOV - KI - UR - 49.82 - 23.95 - 325 - 0 - - - - UKOO - 338370 - ODESSA/TSENTRALNY - KI - UR - 46.43 - 30.77 - 35 - 0 - - - - ULAA - 225500 - ARHANGELSK - AR - RS - 64.53 - 40.47 - 13 - 0 - - - - ULLI - 260630 - ST.PETERSBURG(VOEJKOVO) - LE - RS - 59.95 - 30.70 - 78 - 0 - - - - ULLL - 260630 - ST.PETERSBURG - LE - RS - 59.95 - 30.70 - 78 - 0 - - - - ULWW - 270370 - VOLOGDA - AR - RS - 59.23 - 39.87 - 131 - 0 - - - - ULMM - 221130 - MURMANSK - AR - RS - 68.97 - 33.05 - 51 - 0 - - - - UMKK - 267020 - KALININGRAD - - BY - 54.70 - 20.62 - 27 - 0 - - - - UMMS - 268500 - MINSK - MI - BY - 53.87 - 27.53 - 234 - 0 - - - - UNBB - 298380 - BARNAUL - NO - RA - 53.40 - 83.70 - 252 - 0 - - - - UNIT - 245070 - TURA - NO - RA - 64.27 - 100.23 - 186 - 0 - - - - UNKB - 292820 - BOGUCHANY - NO - RA - 58.42 - 97.40 - 134 - 0 - - - - UNKL - 284935 - KRASNOYARSK - - RS - 56.18 - 92.52 - -9999 - 0 - - - - UNLL - 999999 - KOLPASHEVO - NO - RA - 58.30 - 82.90 - 76 - 0 - - - - UNNT - 296340 - NOVOSIBIRSK/TOLMACH - - RA - 55.03 - 82.90 - 177 - 0 - - - - UNOO - 286980 - OMSK - NO - RA - 54.93 - 73.40 - 123 - 0 - - - - UODD - 206740 - DIKSON_ISLAND - DK - RA - 73.53 - 80.40 - 47 - 0 - - - - UOHH - 208910 - KHATANGA - DK - RA - 71.98 - 102.47 - 24 - 0 - - - - UOTT - 234720 - TURUKHANSK - - RA - 65.78 - 087.95 - 37 - 0 - - - - URRV - 273290 - ROSTOV - MS - RS - 57.20 - 39.42 - 102 - 0 - - - - URWA - 999999 - ASTRAKHAN - - RS - 46.35 - 47.97 - -22 - 0 - - - - URWW - 345600 - VOLGOGRAD - TB - RS - 48.68 - 44.35 - 145 - 0 - - - - USCC - 286420 - CHELYABINSK/BALANDI - SV - RA - 55.18 - 61.32 - -9999 - 0 - - - - USDD - 999999 - SALEKHARD - NO - RA - 66.53 - 66.53 - 358 - 0 - - - - USDS - 235520 - TARKO-SALE - NO - RA - 64.92 - 77.82 - 27 - 0 - - - - USHB - 236310 - BEREZOVO - NO - RA - 63.93 - 65.05 - 27 - 0 - - - - USHH - 239330 - HANTY-MANSIJSK - NO - RA - 60.97 - 69.07 - 40 - 0 - - - - USKK - 999999 - KIROV - MS - RS - 58.60 - 49.63 - 158 - 0 - - - - USPP - 282250 - PERM - SV - RA - 58.02 - 56.30 - 172 - 0 - - - - USRR - 238490 - SURGUT - NO - RA - 61.25 - 73.50 - 44 - 0 - - - - USSS - 284400 - SVERDLOVSK - SV - RA - 56.80 - 60.63 - 237 - 0 - - - - USUU - 286610 - KURGAN - SV - RA - 55.47 - 65.40 - 79 - 0 - - - - UTAA - 388800 - ASHABAD - TA - RA - 37.97 - 58.33 - 210 - 0 - - - - UTTT - 384570 - TASHKENT/YUZNI - TA - RA - 41.27 - 69.27 - 489 - 0 - - - - UUWW - 275185 - MOSCOW/VNUKOVO - MS - RS - 55.65 - 37.27 - 203 - 0 - - - - UUYP - 234180 - PECHORA - AR - RS - 65.11 - 57.10 - 61 - 0 - - - - UUYW - 232260 - VORKUTA - AR - RA - 67.48 - 64.02 - 180 - 0 - - - - UUYY - 238040 - SYKTYVKAR - AR - RA - 61.72 - 50.83 - 119 - 0 - - - - UWKD - 275950 - KAZAN' - MS - RS - 55.78 - 49.18 - 116 - 0 - - - - UWUU - 287220 - UFA - SV - RA - 54.75 - 56.00 - 105 - 0 - - - - VABB - 430030 - BOMBAY/SANTA_CR - - IN - 19.12 - 72.84 - 14 - 0 - - - - VCBI - 434500 - COLOMBO/KATUNAYAKE - - SB - 7.17 - 79.88 - 8 - 0 - - - - VGZR - 419220 - KURMITOLA/ZIA_INTL - - BW - 23.85 - 90.40 - 10 - 0 - - - - VHHH - 450070 - HONG_KONG_INTL_ARPT - - HK - 22.33 - 114.18 - 24 - 0 - - - - VIDP - 421810 - INDIRA_GANDHI/DELHI - - IN - 28.57 - 77.12 - 233 - 0 - - - - WSSS - 486980 - SINGAPORE/CHANG - - SR - 1.37 - 103.98 - 16 - 0 - - - - YBRF - 945780 - BRISBANE - QU - AU - -27.43 - 153.08 - 2 - 0 - - - - YBTL - 942940 - TOWNSVILLE(CIV/MIL) - QU - AU - -19.25 - 146.75 - 6 - 0 - - - - YMHF - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YMMB - 948700 - MOORABBIN_AIRPORT - VC - AU - -37.98 - 145.10 - 13 - 0 - - - - YMMC - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YMRF - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YPDM - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YPRF - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YPRM - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - YSRF - 948640 - MELBOURNE - VC - AU - -37.73 - 144.90 - 81 - 0 - - - - ZPPP - 567780 - KUNMING/WUJIABA - CD - CI - 25.02 - 102.68 - 1892 - 0 - - - + + + + + YSJ + 000395 + ST_JOHN + NB + CN + 45.32 + -65.88 + 0 + 0 + + + + HUL + 000341 + HOULTON + ME + US + 46.04 + -67.83 + 0 + 0 + + + + PQI + 000367 + PRESQUE_ISLE + ME + US + 46.77 + -68.09 + 0 + 0 + + + + MLT + 000183 + MILLINOCKET + ME + US + 45.58 + -68.52 + 0 + 0 + + + + BGR + 000029 + BANGOR + ME + US + 44.84 + -68.87 + 0 + 0 + + + + ACK + 000005 + NANTUCKET + MA + US + 41.28 + -70.03 + 0 + 0 + + + + ENE + 000322 + KENNEBUNK + ME + US + 43.43 + -70.61 + 0 + 0 + + + + BOS + 000289 + BOSTON + MA + US + 42.36 + -70.99 + 0 + 0 + + + + YQB + 000391 + QUEBEC + QB + CN + 46.80 + -71.38 + 0 + 0 + + + + PVD + 000221 + PROVIDENCE + RI + US + 41.72 + -71.43 + 0 + 0 + + + + CON + 000062 + CONCORD + NH + US + 43.22 + -71.58 + 0 + 0 + + + + YSC + 000394 + SHERBROOKE + QB + CN + 45.43 + -71.68 + 0 + 0 + + + + HTO + 000340 + EAST_HAMPTON + NY + US + 40.92 + -72.32 + 0 + 0 + + + + MPV + 000188 + MONTPELIER + VT + US + 44.22 + -72.57 + 0 + 0 + + + + BDL + 000287 + WINSOR_LOCKS + CT + US + 41.94 + -72.69 + 0 + 0 + + + + PLB + 000365 + PLATTSBURGH + NY + US + 44.69 + -73.52 + 0 + 0 + + + + JFK + 000345 + NEW_YORK/JF_KENNEDY + NY + US + 40.63 + -73.77 + 0 + 0 + + + + ALB + 000012 + ALBANY + NY + US + 42.75 + -73.80 + 0 + 0 + + + + CYN + 000300 + COYLE + NJ + US + 39.82 + -74.43 + 0 + 0 + + + + SAX + 000376 + SPARTA + NJ + US + 41.07 + -74.54 + 0 + 0 + + + + MSS + 000353 + MASSENA + NY + US + 44.91 + -74.72 + 0 + 0 + + + + SIE + 000377 + SEA_ISLE + NJ + US + 39.10 + -74.80 + 0 + 0 + + + + HNK + 000338 + HANCOCK + NY + US + 42.06 + -75.32 + 0 + 0 + + + + SBY + 000242 + SALISBURY + MD + US + 38.35 + -75.52 + 0 + 0 + + + + YOW + 000390 + OTTAWA + ON + CN + 45.32 + -75.67 + 0 + 0 + + + + ETX + 000325 + EAST_TEXAS + PA + US + 40.58 + -75.68 + 0 + 0 + + + + ECG + 000086 + ELIZABETH_CITY + NC + US + 36.25 + -76.18 + 0 + 0 + + + + SYR + 000259 + SYRACUSE + NY + US + 43.16 + -76.20 + 0 + 0 + + + + ORF + 000203 + NORFOLK + VA + US + 36.89 + -76.20 + 0 + 0 + + + + EMI + 000320 + WESTMINSTER + MD + US + 39.50 + -76.98 + 0 + 0 + + + + HAR + 000126 + HARRISBURG + PA + US + 40.23 + -77.02 + 0 + 0 + + + + DCA + 000306 + WASHINGTON + DC + US + 38.86 + -77.04 + 0 + 0 + + + + RIC + 000229 + RICHMOND + VA + US + 37.50 + -77.32 + 0 + 0 + + + + CSN + 000299 + CASSANOVA + VA + US + 38.64 + -77.87 + 0 + 0 + + + + ILM + 000135 + WILMINGTON + NC + US + 34.35 + -77.87 + 0 + 0 + + + + SLT + 000252 + SLATE_RUN + PA + US + 41.51 + -77.97 + 0 + 0 + + + + PSB + 000368 + PHILLIPSBURG + PA + US + 40.92 + -77.99 + 0 + 0 + + + + BUF + 000044 + BUFFALO + NY + US + 42.93 + -78.65 + 0 + 0 + + + + RDU + 000372 + RALEIGH-DURHAM + NC + US + 35.87 + -78.78 + 0 + 0 + + + + JST + 000145 + JOHNSTOWN + PA + US + 40.32 + -78.83 + 0 + 0 + + + + JHW + 000346 + JAMESTOWN + NY + US + 42.19 + -79.12 + 0 + 0 + + + + LYH + 000166 + LYNCHBURG + VA + US + 37.25 + -79.23 + 0 + 0 + + + + YYZ + 000401 + TORONTO + ON + CN + 43.67 + -79.63 + 0 + 0 + + + + FLO + 000102 + FLORENCE + SC + US + 34.23 + -79.66 + 0 + 0 + + + + GSO + 000122 + GREENSBORO + NC + US + 36.05 + -79.98 + 0 + 0 + + + + CHS + 000056 + CHARLESTON + SC + US + 32.89 + -80.04 + 0 + 0 + + + + PBI + 000206 + WEST_PALM_BEACH + FL + US + 26.68 + -80.09 + 0 + 0 + + + + EKN + 000088 + ELKINS + WV + US + 38.92 + -80.10 + 0 + 0 + + + + EWC + 000326 + ELLWOOD_CITY + PA + US + 40.83 + -80.21 + 0 + 0 + + + + ERI + 000092 + ERIE + PA + US + 42.02 + -80.30 + 0 + 0 + + + + MIA + 000176 + MIAMI + FL + US + 25.80 + -80.30 + 0 + 0 + + + + TRV + 000276 + TREASURE + FL + US + 27.68 + -80.49 + 0 + 0 + + + + VRB + 000276 + VERO_BEACH + FL + US + 27.68 + -80.49 + 0 + 0 + + + + PSK + 000369 + DUBLIN + VA + US + 37.09 + -80.71 + 0 + 0 + + + + AIR + 000280 + BELLAIRE + OH + US + 40.02 + -80.82 + 0 + 0 + + + + CLT + 000059 + CHARLOTTE + NC + US + 35.22 + -80.93 + 0 + 0 + + + + CAE + 000295 + COLUMBIA + SC + US + 33.86 + -81.05 + 0 + 0 + + + + YVV + 000396 + WIARTON + ON + CN + 44.75 + -81.10 + 0 + 0 + + + + SAV + 000239 + SAVANNAH + GA + US + 32.16 + -81.11 + 0 + 0 + + + + OMN + 000363 + ORMOND_BEACH + FL + US + 29.30 + -81.11 + 0 + 0 + + + + BKW + 000034 + BECKLEY + WV + US + 37.78 + -81.12 + 0 + 0 + + + + ORL + 000204 + ORLANDO + FL + US + 28.54 + -81.34 + 0 + 0 + + + + CRG + 000298 + JACKSONVILLE + FL + US + 30.34 + -81.51 + 0 + 0 + + + + EYW + 000096 + KEY_WEST + FL + US + 24.59 + -81.80 + 0 + 0 + + + + RSW + 000104 + LEE_COUNTY + FL + US + 26.53 + -81.78 + 0 + 0 + + + + FMY + 000104 + FT_MEYERS + FL + US + 26.58 + -81.87 + 0 + 0 + + + + SPA + 000380 + SPARTANBURG + SC + US + 35.03 + -81.93 + 0 + 0 + + + + HNN + 000339 + HENDERSON + WV + US + 38.75 + -82.03 + 0 + 0 + + + + HMV + 000337 + HOLSTON_MOUNTAIN + TN + US + 36.44 + -82.13 + 0 + 0 + + + + CLE + 000058 + CLEVELAND + OH + US + 41.42 + -81.85 + 0 + 0 + + + + IRQ + 000344 + COLLIERS + SC + US + 33.71 + -82.16 + 0 + 0 + + + + AMG + 000015 + ALMA + GA + US + 31.54 + -82.51 + 0 + 0 + + + + SRQ + 000382 + SARASOTA + FL + US + 27.40 + -82.55 + 0 + 0 + + + + APE + 000283 + APPLETON + OH + US + 40.15 + -82.59 + 0 + 0 + + + + PIE + 000212 + SAINT_PETERSBURG + FL + US + 27.91 + -82.68 + 0 + 0 + + + + ECK + 000316 + PECK + MI + US + 43.26 + -82.72 + 0 + 0 + + + + CTY + 000066 + CROSS_CITY + FL + US + 29.60 + -83.05 + 0 + 0 + + + + ODF + 000360 + TOCCOA + GA + US + 34.70 + -83.30 + 0 + 0 + + + + DXO + 000315 + DETROIT + MI + US + 42.21 + -83.37 + 0 + 0 + + + + ASP + 000284 + OSCODA + MI + US + 44.45 + -83.39 + 0 + 0 + + + + MCN + 000170 + MACON + GA + US + 32.69 + -83.65 + 0 + 0 + + + + FNT + 000328 + FLINT + MI + US + 42.97 + -83.74 + 0 + 0 + + + + VXV + 000388 + KNOXVILLE + TN + US + 35.90 + -83.89 + 0 + 0 + + + + ROD + 000373 + ROSEWOOD + OH + US + 40.29 + -84.04 + 0 + 0 + + + + MBS + 000168 + SAGINAW + MI + US + 43.53 + -84.08 + 0 + 0 + + + + LOZ + 000160 + LONDON + KY + US + 37.03 + -84.12 + 0 + 0 + + + + PZD + 000004 + PECAN + GA + US + 31.66 + -84.29 + 0 + 0 + + + + ABY + 000004 + ALBANY + GA + US + 31.65 + -84.30 + 0 + 0 + + + + SSM + 000255 + SAULT_STE_MARIE + MI + US + 46.41 + -84.31 + 0 + 0 + + + + TLH + 000264 + TALLAHASSEE + FL + US + 30.56 + -84.37 + 0 + 0 + + + + ATL + 000019 + ATLANTA + GA + US + 33.63 + -84.44 + 0 + 0 + + + + CVG + 000067 + COVINGTON + KY + US + 39.02 + -84.70 + 0 + 0 + + + + GQO + 000331 + CHATTANOOGA + TN + US + 34.96 + -85.15 + 0 + 0 + + + + FWA + 000109 + FT_WAYNE + IN + US + 40.98 + -85.19 + 0 + 0 + + + + LGC + 000350 + LA_GRANGE + GA + US + 33.05 + -85.21 + 0 + 0 + + + + GRR + 000332 + GRAND_RAPIDS + MI + US + 42.79 + -85.50 + 0 + 0 + + + + TVC + 000270 + TRAVERSE_CITY + MI + US + 44.67 + -85.55 + 0 + 0 + + + + IIU + 000159 + LOUISVILLE + KY + US + 38.10 + -85.58 + 0 + 0 + + + + LOU + 000159 + LOUISVILLE + KY + US + 38.10 + -85.58 + 0 + 0 + + + + MKG + 000179 + MUSKEGON + MI + US + 43.17 + -86.04 + 0 + 0 + + + + PMM + 000366 + PULLMAN + MI + US + 42.47 + -86.11 + 0 + 0 + + + + GIJ + 000330 + NILES + MI + US + 41.77 + -86.32 + 0 + 0 + + + + MGM + 000175 + MONTGOMERY + AL + US + 32.22 + -86.32 + 0 + 0 + + + + IND + 000136 + INDIANAPOLIS + IN + US + 39.81 + -86.37 + 0 + 0 + + + + BWG + 000047 + BOWLING_GREEN + KY + US + 36.93 + -86.44 + 0 + 0 + + + + BNA + 000037 + NASHVILLE + TN + US + 36.14 + -86.68 + 0 + 0 + + + + CEW + 000052 + CRESTVIEW + FL + US + 30.83 + -86.68 + 0 + 0 + + + + VUZ + 000387 + VULCAN + AL + US + 33.67 + -86.90 + 0 + 0 + + + + BVT + 000293 + LAFAYETTE + IN + US + 40.56 + -87.07 + 0 + 0 + + + + TTH + 000384 + TERRE_HAUTE + IN + US + 39.49 + -87.25 + 0 + 0 + + + + MSL + 000191 + MUSCLE_SHOALS + AL + US + 34.70 + -87.48 + 0 + 0 + + + + SAW + 000189 + SAWYER + MI + US + 46.35 + -87.38 + 0 + 0 + + + + PXV + 000370 + POCKET_CITY + IN + US + 37.93 + -87.76 + 0 + 0 + + + + ORD + 000202 + O'HARE_INTERNATIONAL + IL + US + 41.98 + -87.90 + 0 + 0 + + + + GRB + 000119 + GREEN_BAY + WI + US + 44.56 + -88.19 + 0 + 0 + + + + BAE + 000285 + MILWAUKEE + WI + US + 43.12 + -88.28 + 0 + 0 + + + + JOT + 000348 + JOLIET + IL + US + 41.55 + -88.32 + 0 + 0 + + + + SJI + 000378 + SEMMNES + AL + US + 30.73 + -88.36 + 0 + 0 + + + + IGB + 000133 + BIGBEE + MS + US + 33.48 + -88.52 + 0 + 0 + + + + MEI + 000172 + MERIDIAN + MS + US + 32.38 + -88.80 + 0 + 0 + + + + AXC + 000070 + ADDERS + IL + US + 39.74 + -88.86 + 0 + 0 + + + + DEC + 000070 + DECATUR + IL + US + 39.74 + -88.86 + 0 + 0 + + + + YQT + 000393 + THUNDER_BAY + ON + CN + 48.37 + -89.32 + 0 + 0 + + + + DYR + 000083 + DYERSBURG + TN + US + 36.02 + -89.32 + 0 + 0 + + + + RHI + 000228 + RHINELANDER + WI + US + 45.63 + -89.45 + 0 + 0 + + + + BDF + 000024 + BRADFORD + IL + US + 41.16 + -89.59 + 0 + 0 + + + + DLL + 000310 + DELLS + WI + US + 43.55 + -89.76 + 0 + 0 + + + + MEM + 000173 + MEMPHIS + TN + US + 35.06 + -89.98 + 0 + 0 + + + + LEV + 000349 + GRAND_ISLE + LA + US + 29.18 + -90.10 + 0 + 0 + + + + MHZ + 000142 + MAGNOLIA + MS + US + 32.43 + -90.10 + 0 + 0 + + + + JAN + 000142 + JACKSON + MS + US + 32.51 + -90.17 + 0 + 0 + + + + HRV + 000195 + HARVEY + LA + US + 29.85 + -90.00 + 0 + 0 + + + + MSY + 000195 + NEW_ORLEANS + LA + US + 30.00 + -90.27 + 0 + 0 + + + + FAM + 000097 + FARMINGTON + MO + US + 37.67 + -90.23 + 0 + 0 + + + + MCB + 000169 + MC_COMB + MS + US + 31.30 + -90.26 + 0 + 0 + + + + SQS + 000381 + SIDON + MS + US + 33.46 + -90.28 + 0 + 0 + + + + STL + 000257 + ST_LOUIS + MO + US + 38.86 + -90.48 + 0 + 0 + + + + DBQ + 000069 + DUBUQUE + IA + US + 42.40 + -90.71 + 0 + 0 + + + + ARG + 000018 + WALNUT_RIDGE + AR + US + 36.11 + -90.95 + 0 + 0 + + + + UIN + 000386 + QUINCY + IL + US + 39.85 + -91.28 + 0 + 0 + + + + BTR + 000042 + BATON_ROUGE + LA + US + 30.48 + -91.30 + 0 + 0 + + + + ODI + 000361 + NODINE + MN + US + 43.91 + -91.47 + 0 + 0 + + + + EAU + 000085 + EAU_CLAIRE + WI + US + 44.90 + -91.48 + 0 + 0 + + + + IOW + 000343 + IOWA_CITY + IA + US + 41.52 + -91.61 + 0 + 0 + + + + MLU + 000184 + MONROE + LA + US + 32.52 + -92.03 + 0 + 0 + + + + LIT + 000156 + LITTLE_ROCK + AR + US + 34.68 + -92.18 + 0 + 0 + + + + DLH + 000075 + DULUTH + MN + US + 46.80 + -92.20 + 0 + 0 + + + + COU + 000063 + COLUMBIA + MO + US + 38.82 + -92.22 + 0 + 0 + + + + AEX + 000009 + ALEXANDRIA + LA + US + 31.26 + -92.50 + 0 + 0 + + + + IRK + 000139 + KIRKSVILLE + MO + US + 40.14 + -92.59 + 0 + 0 + + + + ELD + 000319 + EL_DORADO + AR + US + 33.26 + -92.74 + 0 + 0 + + + + LCH + 000154 + LAKE_CHARLES + LA + US + 30.14 + -93.11 + 0 + 0 + + + + MSP + 000194 + MINNEAPOLIS + MN + US + 44.88 + -93.23 + 0 + 0 + + + + MCW + 000171 + MASON_CITY + IA + US + 43.09 + -93.33 + 0 + 0 + + + + SGF + 000245 + SPRINGFIELD + MO + US + 37.36 + -93.33 + 0 + 0 + + + + INL + 000137 + INTERNATIONAL_FALLS + MN + US + 48.57 + -93.40 + 0 + 0 + + + + DSM + 000079 + DES_MOINES + IA + US + 41.44 + -93.65 + 0 + 0 + + + + EIC + 000318 + SHREVEPORT + LA + US + 32.77 + -93.81 + 0 + 0 + + + + BRD + 000292 + BRAINERD + MN + US + 46.35 + -94.03 + 0 + 0 + + + + TXK + 000272 + TEXARKANA + AR + US + 33.51 + -94.07 + 0 + 0 + + + + RZC + 000374 + RAZORBACK + AR + US + 36.25 + -94.12 + 0 + 0 + + + + FSM + 000108 + FT_SMITH + AR + US + 35.38 + -94.27 + 0 + 0 + + + + FOD + 000105 + FT_DODGE + IA + US + 42.61 + -94.29 + 0 + 0 + + + + BUM + 000045 + BUTLER + MO + US + 38.27 + -94.49 + 0 + 0 + + + + MCI + 000177 + KANSAS_CITY + MO + US + 39.29 + -94.74 + 0 + 0 + + + + MKC + 000177 + KANSAS_CITY + MO + US + 39.28 + -94.59 + 0 + 0 + + + + LFK + 000155 + LUFKIN + TX + US + 31.16 + -94.72 + 0 + 0 + + + + GGG + 000115 + LONGVIEW + TX + US + 32.42 + -94.75 + 0 + 0 + + + + BJI + 000033 + BEMIDJI + MN + US + 47.58 + -95.02 + 0 + 0 + + + + RWF + 000234 + REDWWOD_FALLS + MN + US + 44.47 + -95.13 + 0 + 0 + + + + OSW + 000205 + OSWEGO + KS + US + 37.15 + -95.20 + 0 + 0 + + + + IAH + 000131 + HOUSTON_INTERNATIONAL + TX + US + 29.96 + -95.35 + 0 + 0 + + + + OVR + 000364 + OMAHA + NE + US + 41.17 + -95.74 + 0 + 0 + + + + MLC + 000180 + MC_CALESTER + OK + US + 34.85 + -95.78 + 0 + 0 + + + + TUL + 000268 + TULSA + OK + US + 36.20 + -95.79 + 0 + 0 + + + + PWE + 000222 + PAWNEE_CITY + NE + US + 40.20 + -96.21 + 0 + 0 + + + + PSX + 000219 + PALACIOS + TX + US + 28.76 + -96.31 + 0 + 0 + + + + FSD + 000107 + SIOUX_FALLS + SD + US + 43.65 + -96.78 + 0 + 0 + + + + FAR + 000098 + FARGO + ND + US + 46.75 + -96.85 + 0 + 0 + + + + TTT + 000072 + MAVERICK + TX + US + 32.87 + -97.04 + 0 + 0 + + + + DFW + 000072 + DALLAS-FT_WORTH + TX + US + 32.87 + -97.03 + 0 + 0 + + + + ADM + 000008 + ARDMORE + OK + US + 34.21 + -97.17 + 0 + 0 + + + + GFK + 000114 + GRAND_FORKS + ND + US + 47.95 + -97.19 + 0 + 0 + + + + YWG + 000397 + WINNIPEG + MB + CN + 49.90 + -97.23 + 0 + 0 + + + + ACT + 000006 + WACO + TX + US + 31.66 + -97.27 + 0 + 0 + + + + BRO + 000041 + BROWNSVILLE + TX + US + 25.92 + -97.38 + 0 + 0 + + + + CRP + 000065 + CORPUS_CHRISTI + TX + US + 27.90 + -97.45 + 0 + 0 + + + + ICT + 000132 + WICHITA + KS + US + 37.75 + -97.58 + 0 + 0 + + + + OKC + 000198 + OKLAHOMA_CITY + OK + US + 35.36 + -97.61 + 0 + 0 + + + + SLN + 000251 + SALINA + KS + US + 38.93 + -97.62 + 0 + 0 + + + + CWK + 000020 + CENTEX + TX + US + 30.38 + -97.53 + 0 + 0 + + + + AUS + 000020 + AUSTIN + TX + US + 30.30 + -97.70 + 0 + 0 + + + + END + 000321 + VANCE_AFB + OK + US + 36.35 + -97.92 + 0 + 0 + + + + OBH + 000358 + WOLBACH + NE + US + 41.38 + -98.35 + 0 + 0 + + + + ABR + 000003 + ABERDEEN + SD + US + 45.42 + -98.37 + 0 + 0 + + + + SAT + 000238 + SAN_ANTONIO + TX + US + 29.64 + -98.46 + 0 + 0 + + + + SPS + 000254 + WICHITA_FALLS + TX + US + 33.99 + -98.59 + 0 + 0 + + + + ONL + 000200 + ONEILL + NE + US + 42.47 + -98.69 + 0 + 0 + + + + LRD + 000161 + LAREDO + TX + US + 27.48 + -99.42 + 0 + 0 + + + + JCT + 000144 + JUNCTION + TX + US + 30.60 + -99.82 + 0 + 0 + + + + ABI + 000001 + ABILENE + TX + US + 32.48 + -99.86 + 0 + 0 + + + + MMB + 000110 + MITBEE + OK + US + 36.34 + -99.88 + 0 + 0 + + + + GAG + 000110 + GAGE + OK + US + 36.34 + -99.88 + 0 + 0 + + + + ANW + 000282 + AINSWORTH + NE + US + 42.57 + -99.99 + 0 + 0 + + + + PIR + 000214 + PIERRE + SD + US + 44.40 + -100.17 + 0 + 0 + + + + HLC + 000335 + HILL_CITY + KS + US + 39.26 + -100.23 + 0 + 0 + + + + CDS + 000051 + CHILDRESS + TX + US + 34.37 + -100.28 + 0 + 0 + + + + SJT + 000248 + SAN_ANGELO + TX + US + 31.38 + -100.46 + 0 + 0 + + + + MCK + 000351 + MC_COOK + NE + US + 40.20 + -100.59 + 0 + 0 + + + + BIS + 000032 + BISMARK + ND + US + 46.77 + -100.67 + 0 + 0 + + + + LBF + 000152 + NORTH_PLATTE + NE + US + 41.13 + -100.72 + 0 + 0 + + + + GCK + 000112 + GARDEN_CITY + KS + US + 37.92 + -100.73 + 0 + 0 + + + + DLF + 000309 + LAUGHLIN_AFB + TX + US + 29.36 + -100.77 + 0 + 0 + + + + LBL + 000153 + LIBERAL + KS + US + 37.04 + -100.97 + 0 + 0 + + + + MOT + 000187 + MINOT + ND + US + 48.26 + -101.29 + 0 + 0 + + + + AMA + 000014 + AMARILLO + TX + US + 35.29 + -101.64 + 0 + 0 + + + + GLD + 000118 + GOODLAND + KS + US + 39.39 + -101.69 + 0 + 0 + + + + DPR + 000077 + DUPREE + SD + US + 45.08 + -101.72 + 0 + 0 + + + + LBB + 000151 + LUBBOCK_INTERNATIONAL + TX + US + 33.70 + -101.92 + 0 + 0 + + + + MAF + 000167 + MIDLAND + TX + US + 32.02 + -102.18 + 0 + 0 + + + + LAA + 000146 + LAMAR + CO + US + 38.20 + -102.69 + 0 + 0 + + + + DIK + 000074 + DICKINSIN + ND + US + 46.86 + -102.77 + 0 + 0 + + + + TXO + 000385 + TEXICO_NM/BOVINA + TX + US + 34.50 + -102.84 + 0 + 0 + + + + SNY + 000379 + SIDNEY + NE + US + 41.10 + -102.98 + 0 + 0 + + + + FST + 000329 + FT_STOCKTON + TX + US + 30.95 + -102.98 + 0 + 0 + + + + RAP + 000224 + RAPID_CITY + SD + US + 43.98 + -103.01 + 0 + 0 + + + + AKO + 000011 + AKRON + CO + US + 40.16 + -103.18 + 0 + 0 + + + + INK + 000342 + WINK + TX + US + 31.87 + -103.24 + 0 + 0 + + + + BFF + 000026 + SCOTTSBLUFF + NE + US + 41.89 + -103.48 + 0 + 0 + + + + TBE + 000261 + TOBE + CO + US + 37.27 + -103.60 + 0 + 0 + + + + TCC + 000262 + TUCUMCARI + NM + US + 35.18 + -103.60 + 0 + 0 + + + + ISN + 000140 + WILLISTON + ND + US + 48.18 + -103.63 + 0 + 0 + + + + MRF + 000190 + MARFA + TX + US + 30.30 + -103.95 + 0 + 0 + + + + PUB + 000220 + PUEBLO + CO + US + 38.29 + -104.43 + 0 + 0 + + + + CME + 000233 + CHISUM + NM + US + 33.34 + -104.62 + 0 + 0 + + + + ROW + 000233 + ROSWELL + NM + US + 33.34 + -104.62 + 0 + 0 + + + + DEN + 000071 + DENVER + CO + US + 39.81 + -104.66 + 0 + 0 + + + + CYS + 000301 + CHEYENNE + WY + US + 41.21 + -104.77 + 0 + 0 + + + + CIM + 000297 + CIMARRON + NM + US + 36.49 + -104.87 + 0 + 0 + + + + FTI + 000163 + FT_UNION + NM + US + 35.66 + -105.14 + 0 + 0 + + + + LVS + 000163 + LAS_VEGAS + NM + US + 35.66 + -105.14 + 0 + 0 + + + + LAR + 000148 + LARAMIE + WY + US + 41.33 + -105.72 + 0 + 0 + + + + ALS + 000013 + ALAMOSA + CO + US + 37.35 + -105.82 + 0 + 0 + + + + MLS + 000182 + MILES_CITY + MT + US + 46.38 + -105.95 + 0 + 0 + + + + DDY + 000307 + CASPER + WY + US + 43.09 + -106.28 + 0 + 0 + + + + ELP + 000090 + EL_PASO + TX + US + 31.82 + -106.28 + 0 + 0 + + + + CZI + 000302 + CRAZY_WOMAN + WY + US + 44.00 + -106.44 + 0 + 0 + + + + GGW + 000116 + GLASGOW + MT + US + 48.22 + -106.63 + 0 + 0 + + + + ABQ + 000002 + ALBUQUERQUE + NM + US + 35.04 + -106.82 + 0 + 0 + + + + DBL + 000304 + EAGLE + CO + US + 39.44 + -106.90 + 0 + 0 + + + + HBU + 000333 + GUNNISON + CO + US + 38.45 + -107.04 + 0 + 0 + + + + SHR + 000246 + SHERIDAN + WY + US + 44.84 + -107.06 + 0 + 0 + + + + TCS + 000263 + TRUTH_OR_CONSEQUENCES + NM + US + 33.28 + -107.28 + 0 + 0 + + + + CHE + 000054 + HAYDEN + CO + US + 40.52 + -107.31 + 0 + 0 + + + + DMN + 000076 + DEMING + NM + US + 32.28 + -107.60 + 0 + 0 + + + + YYN + 000400 + SWIFT_CURRENT + SA + CN + 50.28 + -107.68 + 0 + 0 + + + + RSK + 000103 + RATTLESNAKE + NM + US + 36.75 + -108.10 + 0 + 0 + + + + FMN + 000103 + FARMINGTON + NM + US + 36.75 + -108.10 + 0 + 0 + + + + BOY + 000290 + BOYSEN_RESV. + WY + US + 43.46 + -108.30 + 0 + 0 + + + + BIL + 000031 + BILLINGS + MT + US + 45.81 + -108.63 + 0 + 0 + + + + JNC + 000347 + GRAND_JUNCTION + CO + US + 39.06 + -108.79 + 0 + 0 + + + + DVC + 000082 + DOVE_CREEK + CO + US + 37.81 + -108.93 + 0 + 0 + + + + OCS + 000359 + ROCKSPRINGS + WY + US + 41.59 + -109.02 + 0 + 0 + + + + SJN + 000247 + ST_JOHNS + AZ + US + 34.42 + -109.14 + 0 + 0 + + + + SSO + 000256 + SAN_SIMON + AZ + US + 32.27 + -109.26 + 0 + 0 + + + + LWT + 000165 + LEWISTOWN + MT + US + 47.05 + -109.61 + 0 + 0 + + + + HVR + 000129 + HAVRE + MT + US + 48.54 + -109.77 + 0 + 0 + + + + BPI + 000291 + BIG_PINEY + WY + US + 42.58 + -110.11 + 0 + 0 + + + + MTU + 000196 + MYTON + UT + US + 40.15 + -110.13 + 0 + 0 + + + + HVE + 000128 + HANKSVILLE + UT + US + 38.42 + -110.70 + 0 + 0 + + + + YXH + 000399 + MEDICINE_HAT + AB + CN + 50.02 + -110.72 + 0 + 0 + + + + JAC + 000141 + JACKSON + WY + US + 43.62 + -110.73 + 0 + 0 + + + + INW + 000138 + WINSLOW + AZ + US + 35.06 + -110.80 + 0 + 0 + + + + TUS + 000269 + TUCSON + AZ + US + 32.10 + -110.92 + 0 + 0 + + + + TBC + 000260 + TUBA_CITY + AZ + US + 36.12 + -111.27 + 0 + 0 + + + + GTF + 000123 + GREAT_FALLS + MT + US + 47.45 + -111.41 + 0 + 0 + + + + HLN + 000336 + HELENA + MT + US + 46.61 + -111.95 + 0 + 0 + + + + PHX + 000211 + PHOENIX + AZ + US + 33.43 + -112.02 + 0 + 0 + + + + SLC + 000249 + SALT_LAKE_CITY + UT + US + 40.85 + -111.98 + 0 + 0 + + + + DBS + 000305 + DUBOIS + ID + US + 44.09 + -112.21 + 0 + 0 + + + + BCE + 000023 + BRYCE_CANYON + UT + US + 37.69 + -112.30 + 0 + 0 + + + + MLD + 000352 + MALAD_CITY + ID + US + 42.20 + -112.45 + 0 + 0 + + + + DRK + 000313 + PRESCOTT + AZ + US + 34.70 + -112.48 + 0 + 0 + + + + DTA + 000080 + DELTA + UT + US + 39.30 + -112.51 + 0 + 0 + + + + DLN + 000311 + DILLON + MT + US + 45.25 + -112.55 + 0 + 0 + + + + PIH + 000213 + POCATELLO + ID + US + 42.87 + -112.65 + 0 + 0 + + + + YQL + 000392 + LETHBRIDGE + AB + CN + 49.63 + -112.80 + 0 + 0 + + + + PGS + 000210 + PEACH_SPRINGS + AZ + US + 35.62 + -113.54 + 0 + 0 + + + + BVL + 000046 + BOONEVILLE + UT + US + 40.73 + -113.76 + 0 + 0 + + + + LKT + 000157 + SALMON + ID + US + 45.02 + -114.08 + 0 + 0 + + + + FCA + 000100 + KALISPELL + MT + US + 48.21 + -114.18 + 0 + 0 + + + + ILC + 000134 + WILSON_CREEK + NV + US + 38.25 + -114.39 + 0 + 0 + + + + EED + 000087 + NEEDLES + CA + US + 34.77 + -114.47 + 0 + 0 + + + + TWF + 000271 + TWIN_FALLS + ID + US + 42.48 + -114.49 + 0 + 0 + + + + BZA + 000294 + YUMA + AZ + US + 32.77 + -114.60 + 0 + 0 + + + + ELY + 000091 + ELY + NV + US + 39.30 + -114.85 + 0 + 0 + + + + LAS + 000149 + LAS_VEGAS + NV + US + 36.08 + -115.16 + 0 + 0 + + + + MLP + 000181 + MULLAN_PASS + ID + US + 47.46 + -115.65 + 0 + 0 + + + + YXC + 000398 + CRANBROOK + BC + CN + 49.60 + -115.78 + 0 + 0 + + + + TRM + 000383 + THERMAL + CA + US + 33.63 + -116.16 + 0 + 0 + + + + BOI + 000039 + BOISE + ID + US + 43.55 + -116.19 + 0 + 0 + + + + DNJ + 000312 + MC_CALL + ID + US + 44.77 + -116.21 + 0 + 0 + + + + HEC + 000334 + HECTOR + CA + US + 34.80 + -116.46 + 0 + 0 + + + + BTY + 000043 + BEATTY + NV + US + 36.80 + -116.75 + 0 + 0 + + + + BAM + 000286 + BATTLE_MOUNTAIN + NV + US + 40.57 + -116.92 + 0 + 0 + + + + MZB + 000354 + MISSION_BAY + CA + US + 32.78 + -117.23 + 0 + 0 + + + + GEG + 000113 + SPOKANE + WA + US + 47.56 + -117.63 + 0 + 0 + + + + OAL + 000357 + COALDALE + NV + US + 38.00 + -117.77 + 0 + 0 + + + + BKE + 000288 + BAKER + OR + US + 44.84 + -117.81 + 0 + 0 + + + + REO + 000227 + ROME + OR + US + 42.59 + -117.87 + 0 + 0 + + + + LAX + 000150 + LOS_ANGELES_INTL + CA + US + 33.93 + -118.43 + 0 + 0 + + + + PDT + 000207 + PENDLETON + OR + US + 45.70 + -118.94 + 0 + 0 + + + + EHF + 000317 + BAKERSFIELD + CA + US + 35.48 + -119.10 + 0 + 0 + + + + EPH + 000324 + EPHRATA + WA + US + 47.38 + -119.42 + 0 + 0 + + + + FMG + 000327 + RENO + NV + US + 39.53 + -119.66 + 0 + 0 + + + + RZS + 000375 + SANTA_BARBARA + CA + US + 34.51 + -119.77 + 0 + 0 + + + + CZQ + 000303 + FRESNO + CA + US + 36.88 + -119.82 + 0 + 0 + + + + YKM + 000279 + YAKIMA + WA + US + 46.57 + -120.45 + 0 + 0 + + + + LKV + 000158 + LAKEVIEW + OR + US + 42.49 + -120.51 + 0 + 0 + + + + YDC + 000389 + PRINCETON + BC + CN + 49.47 + -120.52 + 0 + 0 + + + + MOD + 000186 + MODESTO + CA + US + 37.63 + -120.96 + 0 + 0 + + + + DSD + 000314 + REDMOND + WA + US + 44.25 + -121.30 + 0 + 0 + + + + SAC + 000236 + SACRAMENTO + CA + US + 38.44 + -121.55 + 0 + 0 + + + + SNS + 000253 + SALINAS + CA + US + 36.66 + -121.60 + 0 + 0 + + + + OAK + 000356 + OAKLAND + CA + US + 37.73 + -122.22 + 0 + 0 + + + + RBL + 000225 + RED_BLUFF + CA + US + 40.10 + -122.24 + 0 + 0 + + + + SEA + 000243 + SEATTLE + WA + US + 47.44 + -122.31 + 0 + 0 + + + + HUH + 000035 + WHATCOM + WA + US + 48.95 + -122.58 + 0 + 0 + + + + BLI + 000035 + BELLINGHAM + WA + US + 48.95 + -122.58 + 0 + 0 + + + + PDX + 000208 + PORTLAND + OR + US + 45.58 + -122.60 + 0 + 0 + + + + PYE + 000371 + POINT_REYES + CA + US + 38.08 + -122.87 + 0 + 0 + + + + OED + 000362 + MEDFORD + OR + US + 42.48 + -122.91 + 0 + 0 + + + + EUG + 000093 + EUGENE + OR + US + 44.12 + -123.22 + 0 + 0 + + + + ENI + 000323 + UKIAH + CA + US + 39.05 + -123.27 + 0 + 0 + + + + ONP + 000201 + NEWPORT + OR + US + 44.58 + -124.06 + 0 + 0 + + + + HQM + 000127 + HOQUIAM + WA + US + 46.95 + -124.15 + 0 + 0 + + + + FOT + 000106 + FORTUNA + CA + US + 40.67 + -124.23 + 0 + 0 + + + + TOU + 000265 + NEAH_BAY + WA + US + 48.30 + -124.63 + 0 + 0 + + + + YQV + 000402 + YORKTON + SA + CN + 51.27 + -102.47 + 0 + 0 + + + + ANN + 0 + ANNETTE_ISLAND + AK + US + 55.05 + -131.57 + 0 + 0 + + + + LVD + 0 + LEVEL_ISLAND + AK + US + 56.47 + -133.08 + 0 + 0 + + + + BKA + 0 + BIORKA_ISLAND + AK + US + 56.86 + -135.55 + 0 + 0 + + + + SSR + 0 + SISTERS_ISLAND + AK + US + 58.17 + -135.25 + 0 + 0 + + + + JNU + 0 + JUNEAU + AK + US + 58.35 + -134.58 + 0 + 0 + + + + YAK + 0 + YAKUTAT + AK + US + 59.50 + -139.67 + 0 + 0 + + + + MDO + 0 + MIDDLETON_ISLAND + AK + US + 59.45 + -146.30 + 0 + 0 + + + + JOH + 0 + JOHNSTONE_POINT + AK + US + 60.48 + -146.60 + 0 + 0 + + + + ODK + 0 + KODIAK + AK + US + 57.75 + -152.50 + 0 + 0 + + + + HOM + 0 + HOMER + AK + US + 59.65 + -151.48 + 0 + 0 + + + + ENA + 0 + KENAI + AK + US + 60.57 + -151.25 + 0 + 0 + + + + ANC + 0 + ANCHORAGE + AK + US + 61.17 + -150.00 + 0 + 0 + + + + BGQ + 0 + BIG_LAKE + AK + US + 61.53 + -149.82 + 0 + 0 + + + + ORT + 0 + NORTHWAY + AK + US + 62.97 + -141.93 + 0 + 0 + + + + GKN + 0 + GULKANA + AK + US + 62.15 + -145.45 + 0 + 0 + + + + TKA + 0 + TALKEETNA + AK + US + 62.32 + -150.10 + 0 + 0 + + + + SQA + 0 + SPARREVOHN + AK + US + 61.10 + -155.63 + 0 + 0 + + + + DLG + 0 + DILLINGHAM + AK + US + 59.05 + -158.50 + 0 + 0 + + + + AKN + 0 + KING_SALMON + AK + US + 58.68 + -156.65 + 0 + 0 + + + + PDN + 0 + PORT_HEIDEN + AK + US + 56.95 + -158.65 + 0 + 0 + + + + CDB + 0 + COLD_BAY + AK + US + 55.20 + -162.73 + 0 + 0 + + + + DUT + 0 + DUTCH_HARBOR + AK + US + 53.90 + -166.55 + 0 + 0 + + + + NUD + 0 + ADAK + AK + US + 51.88 + -176.65 + 0 + 0 + + + + SYA + 0 + SHEMYA + AK + US + 52.72 + 174.12 + 0 + 0 + + + + SPY + 0 + ST_PAUL_ISLAND + AK + US + 57.17 + -170.22 + 0 + 0 + + + + EHM + 0 + CAPE_NEWENHAM + AK + US + 58.66 + -162.07 + 0 + 0 + + + + HPB + 0 + HOOPER_BAY + AK + US + 61.52 + -166.14 + 0 + 0 + + + + BET + 0 + BETHEL + AK + US + 60.78 + -161.83 + 0 + 0 + + + + ANI + 0 + ANIAK + AK + US + 61.59 + -159.61 + 0 + 0 + + + + SMA + 0 + ST_MARYS + AK + US + 62.06 + -163.30 + 0 + 0 + + + + UNK + 0 + UNALAKLEET + AK + US + 63.88 + -160.80 + 0 + 0 + + + + ULL + 0 + KUKULIAK + AK + US + 63.70 + -170.48 + 0 + 0 + + + + MCG + 0 + MC_GRATH + AK + US + 62.95 + -155.60 + 0 + 0 + + + + ENN + 0 + NENANA + AK + US + 64.55 + -149.07 + 0 + 0 + + + + FAI + 0 + FAIRBANKS + AK + US + 64.82 + -147.85 + 0 + 0 + + + + BIG + 0 + BIG_DELTA + AK + US + 64.00 + -145.72 + 0 + 0 + + + + FYU + 0 + FORT_YUKON + AK + US + 66.57 + -145.25 + 0 + 0 + + + + BTT + 0 + BETTLES + AK + US + 66.92 + -151.53 + 0 + 0 + + + + TAL + 0 + TANANA + AK + US + 65.18 + -152.18 + 0 + 0 + + + + CQR + 0 + CHANDALAR_LAKE + AK + US + 67.50 + -148.47 + 0 + 0 + + + + SCC + 0 + DEADHORSE + AK + US + 70.20 + -148.47 + 0 + 0 + + + + BTI + 0 + BARTER_ISLAND + AK + US + 70.13 + -143.57 + 0 + 0 + + + + BRW + 0 + BARROW + AK + US + 71.28 + -156.77 + 0 + 0 + + + + GAL + 0 + GALENA + AK + US + 64.73 + -156.93 + 0 + 0 + + + + OME + 0 + NOME + AK + US + 64.52 + -165.45 + 0 + 0 + + + + OTZ + 0 + KOTZEBUE + AK + US + 66.88 + -162.60 + 0 + 0 + + + + WLK + 0 + SELAWIK + AK + US + 66.60 + -160.00 + 0 + 0 + + + + HSL + 0 + HUSLIA + AK + US + 65.71 + -156.37 + 0 + 0 + + + + BSF + 0 + BRADSHAW + HI + US + 19.76 + -155.39 + 0 + 0 + + + + UPP + 0 + UPOLU_POINT + HI + US + 20.20 + -155.84 + 0 + 0 + + + + ITO + 0 + HILO + HI + US + 19.72 + -155.01 + 0 + 0 + + + + HNL + 0 + HONOLULU + HI + US + 21.33 + -157.93 + 0 + 0 + + + + OGG + 0 + MAUI + HI + US + 20.91 + -156.42 + 0 + 0 + + + + NDB + 0 + VALLEY_ISLAND + HI + US + 20.88 + -156.44 + 0 + 0 + + + + MUE + 0 + KAMUELA + HI + US + 20.00 + -155.67 + 0 + 0 + + + + NGF + 0 + KANEOHE_BAY + HI + US + 21.45 + -157.76 + 0 + 0 + + + + MKK + 0 + MOLOKAI + HI + US + 21.14 + -157.17 + 0 + 0 + + + + NBS + 0 + BARKING_SANDS + HI + US + 22.04 + -159.79 + 0 + 0 + + + + CKH + 0 + KOKO_HEAD + HI + US + 21.27 + -157.70 + 0 + 0 + + + + IAI + 0 + KONA + HI + US + 19.65 + -156.02 + 0 + 0 + + + + LLD + 0 + LANAI + HI + US + 20.77 + -156.97 + 0 + 0 + + + + LNY + 0 + LANAI_CITY + HI + US + 20.76 + -156.97 + 0 + 0 + + + + LIH + 0 + LIHUE + HI + US + 21.97 + -159.34 + 0 + 0 + + + + SOK + 0 + SOUTH_KAUAI + HI + US + 21.90 + -159.53 + 0 + 0 + + + + ASRF + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + AYPY + 940350 + PORT_MORESBY_INTL + + NG + -9.43 + 147.22 + 47 + 0 + + + + BGSF + 042310 + SONDRE_STROMFJORD + + GL + 67.00 + -50.80 + 53 + 0 + + + + BIRK + 040300 + REYKJAVIK + + IL + 64.13 + -21.90 + 61 + 0 + + + + CWEG + 999999 + ALBERTA_WEATHER_CENTRE + + CN + 53.50 + -113.50 + -9999 + 0 + + + + CWLW + 712030 + KELOWNA + BC + CN + 49.95 + -119.40 + 456 + 0 + + + + CWNT + 712500 + TURTLE_MOUNTAIN + AB + CN + 49.58 + -114.42 + 2164 + 0 + + + + CWTO + 716380 + TORONTO_A_E_S__HQ + ON + CN + 43.78 + -79.47 + 187 + 0 + + + + CWUL + 999999 + QUEBEC_FCST_OFFICE + + CN + 45.50 + -73.68 + -9999 + 0 + + + + CYQX + 718030 + GANDER_INTL_AIRPORT + NF + CN + 48.95 + -54.57 + 151 + 0 + + + + DTTA + 607150 + TUNIS/CARTHAGE + + TS + 36.83 + 10.23 + 4 + 0 + + + + EBBR + 064510 + BRUSSELS_NATIONAL + + BX + 50.90 + 4.53 + 58 + 0 + + + + EDMM + 108680 + MUENCHEN + + DL + 48.25 + 11.58 + 484 + 0 + + + + EDZB + 102380 + BERGEN/HOHNE + + DL + 52.82 + 9.93 + 70 + 0 + + + + EDZE + 104100 + ESSEN/MULHEIM + + DL + 51.40 + 6.97 + 161 + 0 + + + + EDZF + 106370 + FRANKFURT/MAIN + + DL + 50.05 + 8.58 + 112 + 0 + + + + EDZH + 107710 + GAERMERSDORF + + DL + 49.43 + 11.90 + 419 + 0 + + + + EDZM + 108680 + MUENCHEN + + DL + 48.25 + 11.58 + 484 + 0 + + + + EETN + 260380 + TALLIN + + BY + 59.35 + 24.80 + 44 + 0 + + + + EFHK + 029740 + HELSINKI/VANTAA + + FI + 60.32 + 24.97 + 56 + 0 + + + + EFRO + 028450 + ROVANIEMI(CIV/MIL) + + FI + 66.57 + 25.83 + 201 + 0 + + + + EGJJ + 038950 + JERSEY_AIRPORT + + UK + 49.22 + -2.20 + 84 + 0 + + + + EHAM + 062400 + AMSTERDAM/SCHIPHOL + + NL + 52.30 + 4.77 + -2 + 0 + + + + EHDB + 062600 + DE_BILT + + NL + 52.10 + 5.18 + 4 + 0 + + + + EINN + 039620 + SHANNON_AIRPORT + + IE + 52.70 + -8.92 + 20 + 0 + + + + EKCH + 061800 + COPENHAGEN/KASTRUP + + DN + 55.63 + 12.67 + 5 + 0 + + + + ENMI + 999999 + OSLO + + NO + 59.50 + 10.70 + -9999 + 0 + + + + ENVN + 011520 + BODO + + NO + 67.25 + 14.40 + 8 + 0 + + + + ENVV + 014150 + STAVANGER + + NO + 58.87 + 5.67 + 34 + 0 + + + + EPWA + 123750 + WARSAW/OKECIE + + PL + 52.17 + 20.97 + 107 + 0 + + + + ESNN + 023660 + SUNDSVALL/HARNOSAND + + SN + 62.53 + 17.45 + 10 + 0 + + + + ESSA + 024600 + STOCKHOLM/ARLANDA + + SN + 59.65 + 17.95 + 61 + 0 + + + + EVRA + 999999 + RIGA_AIRPORT + LE + BY + 56.92 + 23.97 + 10 + 0 + + + + EYVI + 267300 + VILNIUS_INTL + MI + BY + 54.63 + 25.28 + 156 + 0 + + + + FAJS + 683680 + JAN_SMUTS + + ZA + -26.13 + 28.23 + 1700 + 0 + + + + FCBB + 644500 + BRAZZAVILLE/MAYA-MA + + CG + -4.25 + 15.25 + 316 + 0 + + + + FTTJ + 647000 + NDJAMENA(CIV/MIL) + + CD + 12.13 + 15.03 + 295 + 0 + + + + GCGC + 999999 + CANARY_ISLANDS + + CR + 28.50 + -16.00 + -9999 + 0 + + + + GMMC + 601550 + CASABLANCA + + MC + 33.57 + -7.67 + 62 + 0 + + + + HECA + 623660 + CAIRO_INTL_AIRPORT + + EG + 30.13 + 31.40 + 74 + 0 + + + + LBSF + 156140 + SOFIA + + BU + 42.65 + 23.38 + 595 + 0 + + + + LBWN + 155520 + VARNA + + BU + 43.20 + 27.92 + 43 + 0 + + + + LCLK + 176090 + LARNACA/LARNAX_ARPT + + CY + 34.88 + 33.63 + 2 + 0 + + + + LDZA + 131310 + ZAGREB/PLESO + + RH + 45.73 + 16.07 + 107 + 0 + + + + LDZO + 999999 + ZAGREB/PLESO + + RH + 45.73 + 16.07 + 107 + 0 + + + + LECB + 081810 + BARCELONA + + SP + 41.28 + 2.07 + 6 + 0 + + + + LEMM + 999999 + MADRID_CNM + + SP + 40.12 + -3.53 + -9999 + 0 + + + + LFBD + 075100 + BORDEAUX/MERIGNAC + + FR + 44.83 + -.70 + 61 + 0 + + + + LFMM + 076500 + MARSEILLE + + FR + 43.45 + 5.22 + 20 + 0 + + + + LFPW + 999999 + PARIS_MET_CENTER + + FR + 48.83 + 2.33 + 75 + 0 + + + + LFRN + 071300 + RENNES/ST.JACQUES + + FR + 48.07 + -1.73 + 37 + 0 + + + + LFST + 071900 + STRASBOURG/ENTZHEIM + + FR + 48.55 + 7.63 + 154 + 0 + + + + LGAT + 167160 + ATHENS/HELLENKION + + GR + 37.90 + 23.73 + 15 + 0 + + + + LHBP + 128390 + BUDAPEST/FERIHEGY + + HU + 47.43 + 19.27 + 185 + 0 + + + + LIMM + 160800 + MILANO/LINATE + + IY + 45.43 + 9.27 + 103 + 0 + + + + LJLJ + 130140 + LJUBLJANA/BRNIK + + LJ + 46.22 + 14.48 + 385 + 0 + + + + LKPR + 115180 + PRAGUE/RUZYNE + + CZ + 50.10 + 14.28 + 365 + 0 + + + + LLBG + 401800 + BEN-GURION(CIV/MIL) + + IS + 32.00 + 34.90 + 49 + 0 + + + + LMML + 165970 + LUQA/MALTA + + ML + 35.85 + 14.48 + 91 + 0 + + + + LOWW + 110360 + VIENNA/SCHWECHAT + + OS + 48.12 + 16.57 + 190 + 0 + + + + LPPT + 085360 + LISBON/PORTELA + + PO + 38.78 + -9.13 + 123 + 0 + + + + LROM + 154210 + BUCHAREST/OTOPENI + + RO + 44.55 + 26.10 + 95 + 0 + + + + LROP + 154210 + BUCHAREST/OTOPENI + + RO + 44.55 + 26.10 + 95 + 0 + + + + LSZH + 066700 + ZURICH-KLOTEN_(AUT) + + SW + 47.48 + 8.53 + 432 + 0 + + + + LTAC + 171280 + ANKARA/ESENBOGA + + TU + 40.11 + 32.97 + 949 + 0 + + + + LTBA + 170600 + ISTANBUL/ATATURK_AB + + TU + 40.97 + 28.82 + 37 + 0 + + + + LUKK + 338387 + KISHINAU + + UR + 46.93 + 28.93 + 122 + 0 + + + + LWSK + 135860 + SKOPJE/PETROVAC + + MK + 41.97 + 21.65 + 239 + 0 + + + + LYBE + 132720 + BELGRADE/SURCIN + + YG + 44.82 + 20.28 + 99 + 0 + + + + LZIB + 118160 + BRATISLAVA_IVANKA + + CZ + 48.20 + 17.20 + 130 + 0 + + + + NFFN + 916800 + NANDI/NADI_INTL + + FJ + -17.75 + 177.45 + 18 + 0 + + + + NZDT + 999999 + NEW_ZEALAND + + NZ + -41.00 + 172.50 + -9999 + 0 + + + + NZKL + 999999 + AUCKLAND + + NZ + -37.02 + 174.80 + 6 + 0 + + + + MHTG + 787200 + TEGUCIGALPA/TONCONT + + HO + 14.05 + -87.22 + 994 + 0 + + + + MPTO + 787920 + TOCUMEN/GEN._OMAR + + PM + 9.05 + -79.37 + 11 + 0 + + + + OBBB + 999999 + BAHRAIN_INTL_ARPT + + BN + 26.27 + 50.65 + 2 + 0 + + + + OBBI + 411500 + BAHRAIN_INTL_ARPT + + BN + 26.27 + 50.65 + 2 + 0 + + + + OEJD + 999999 + JEDDAH + + SD + 21.30 + 39.20 + -9999 + 0 + + + + OEJN + 410240 + JEDDAH/KING_ABD + + SD + 21.67 + 39.15 + 12 + 0 + + + + OIII + 407540 + TEHRAN/MEHRABAD_AFB + + IR + 35.68 + 51.35 + 1191 + 0 + + + + OIIX + 999999 + TEHRAN + + IR + 35.68 + 51.35 + 1191 + 0 + + + + OLBA + 401000 + BEIRUT_(CIV/MIL) + + LB + 33.82 + 35.48 + 19 + 0 + + + + OPKC + 417800 + KARACHI_INTL_ARPT + + PK + 24.90 + 67.13 + 22 + 0 + + + + OPLA + 416410 + LAHORE(CIV/MIL) + + PK + 31.52 + 74.40 + 217 + 0 + + + + OYSN + 413440 + SANA'A + + YE + 15.52 + 44.18 + 2190 + 0 + + + + PAFA + 702610 + FAIRBANKS_INTL_ARPT_(ASOS) + AK + US + 64.82 + -147.87 + 138 + 0 + + + + PAJN + 703810 + JUNEAU_INTL_AIRPORT_(ASOS) + AK + US + 58.37 + -134.58 + 7 + 0 + + + + PANC + 702730 + ANCHORAGE_INTL_ARPT_(ASOS) + AK + US + 61.17 + -150.02 + 40 + 0 + + + + RKSI + 470699 + CHAJANG_NI_(K-ARMY) + + KO + 37.87 + 127.18 + 100 + 0 + + + + RCTP + 466860 + TAIPEI/CHIANG_KAI_SHEK + + TW + 25.08 + 121.22 + 33 + 0 + + + + SABE + 875820 + AEROPARQUE(CIV/MIL) + + AG + -34.57 + -58.42 + 6 + 0 + + + + SACO + 873440 + CORDOBA_AIRPORT + + AG + -31.32 + -64.22 + 474 + 0 + + + + SAEZ + 875760 + BUENOS_AIRES/EZEIZA + + AG + -34.82 + -58.53 + 20 + 0 + + + + SAME + 874180 + MENDOZA/EL_PLUMERIL + + AG + -32.83 + -68.78 + 704 + 0 + + + + SARE + 871550 + RESISTENCIA_AIRPORT + + AG + -27.45 + -59.05 + 52 + 0 + + + + SBBE + 821930 + BELEM/VAL_DE_CAES + + BZ + -1.38 + -48.48 + 16 + 0 + + + + SBBR + 833780 + BRASILIA_(CIV/MIL) + + BZ + -15.87 + -47.93 + 1061 + 0 + + + + SBBS + 833780 + BRASILIA + + BZ + -15.87 + -47.93 + 1061 + 0 + + + + SBCT + 838400 + CURITIBA/AFONSO_PEN + + BZ + -25.52 + -49.17 + 908 + 0 + + + + SBCW + 838400 + CURITIBA/AFONSO_PEN + + BZ + -25.52 + -49.17 + 908 + 0 + + + + SBEG + 821110 + EDUARDO_GOMES_INTL + + BZ + -3.03 + -60.05 + 2 + 0 + + + + SBGL + 837460 + GALEAO/RIO(CIV/MIL) + + BZ + -22.82 + -43.25 + 6 + 0 + + + + SBGR + 837753 + GUARULHOS_(CIV/MIL) + + BZ + -23.43 + -46.47 + 750 + 0 + + + + SBRF + 828990 + RECIFE/GUARARAPES + + BZ + -8.07 + -34.85 + 19 + 0 + + + + SCCI + 859340 + PUNTA_ARENAS/PRES_C + + CH + -53.00 + -70.85 + 37 + 0 + + + + SCEL + 855740 + PUDAHUEL/ARTURO_MER + + CH + -33.38 + -70.78 + 476 + 0 + + + + SCFA + 854420 + ANTOFAGASTA/CERRO + + CH + -23.43 + -70.43 + 120 + 0 + + + + SCTE + 857990 + PUERTO_MONTT/TEPUAL + + CH + -41.42 + -73.08 + 86 + 0 + + + + SLLP + 852010 + LA_PAZ/JFK_INTL + + BO + -16.52 + -68.18 + 4014 + 0 + + + + SOCA + 814050 + CAYENNE/ROCHAMBEAU + + FG + 4.83 + -52.37 + 9 + 0 + + + + SPIM + 846280 + LIMA/JORGE_CHAVEZ + + PR + -12.00 + -77.12 + 13 + 0 + + + + TNCC + 789880 + HATO_ARPT_(CIV/MIL) + + NU + 12.20 + -68.97 + 67 + 0 + + + + TTPP + 789700 + PIARCO_INTL_AIRPORT + + TD + 10.62 + -61.35 + 15 + 0 + + + + UAAA + 368700 + ALMA-ATA + AL + RA + 43.23 + 76.93 + 847 + 0 + + + + UAFM + 835300 + FRUNZE + + RA + 42.85 + 74.53 + 760 + 0 + + + + UATT + 352290 + AKTJUBINSK + AL + KZ + 50.30 + 57.23 + 227 + 0 + + + + UBBB + 378640 + BAKU/BINE_ARPT + TB + AJ + 40.45 + 50.07 + -1 + 0 + + + + UGEE + 377890 + YEREVAN/ZAPADNY + TB + RS + 40.13 + 44.47 + 907 + 0 + + + + UGGG + 375490 + TBILISI/NOVO-AL + TB + RS + 41.68 + 44.95 + 490 + 0 + + + + UHBB + 315100 + BLAGOVESHCHENSK + HA + RA + 50.27 + 127.50 + 137 + 0 + + + + UHHH + 317350 + KHABAROVSK/NOVY + HA + RA + 48.52 + 135.16 + 72 + 0 + + + + UHNN + 999999 + NIKOLAEVSK-NA-AMURE_CENTER + HA + RA + 53.15 + 140.70 + 68 + 0 + + + + UHWW + 319600 + VLADIVOSTOK + HA + RA + 43.12 + 131.94 + 184 + 0 + + + + UHPP + 325400 + PETROPAVLOVSK-KAMCA + HA + RA + 52.97 + 158.75 + 24 + 0 + + + + UHSS + 321500 + JUZNO-SAHALINSK + HA + RA + 46.92 + 142.73 + 31 + 0 + + + + UIBB + 303090 + BRATSK + + RA + 56.07 + 101.83 + 489 + 0 + + + + UIII + 307100 + IRKUTSK + IR + RA + 52.27 + 104.35 + 513 + 0 + + + + UIKK + 302300 + KIRENSK + IR + RA + 57.77 + 108.07 + 258 + 0 + + + + UKBB + 333470 + BORISPOL'/KIEV + KV + UR + 50.33 + 30.97 + 119 + 0 + + + + UKFF + 339460 + SIMFEROPOL + + UR + 44.68 + 34.13 + 180 + 0 + + + + UKHH + 343000 + KHARKIV + KI + UR + 49.96 + 36.13 + 1550 + 0 + + + + UKLL + 333930 + LVOV + KI + UR + 49.82 + 23.95 + 325 + 0 + + + + UKOO + 338370 + ODESSA/TSENTRALNY + KI + UR + 46.43 + 30.77 + 35 + 0 + + + + ULAA + 225500 + ARHANGELSK + AR + RS + 64.53 + 40.47 + 13 + 0 + + + + ULLI + 260630 + ST.PETERSBURG(VOEJKOVO) + LE + RS + 59.95 + 30.70 + 78 + 0 + + + + ULLL + 260630 + ST.PETERSBURG + LE + RS + 59.95 + 30.70 + 78 + 0 + + + + ULWW + 270370 + VOLOGDA + AR + RS + 59.23 + 39.87 + 131 + 0 + + + + ULMM + 221130 + MURMANSK + AR + RS + 68.97 + 33.05 + 51 + 0 + + + + UMKK + 267020 + KALININGRAD + + BY + 54.70 + 20.62 + 27 + 0 + + + + UMMS + 268500 + MINSK + MI + BY + 53.87 + 27.53 + 234 + 0 + + + + UNBB + 298380 + BARNAUL + NO + RA + 53.40 + 83.70 + 252 + 0 + + + + UNIT + 245070 + TURA + NO + RA + 64.27 + 100.23 + 186 + 0 + + + + UNKB + 292820 + BOGUCHANY + NO + RA + 58.42 + 97.40 + 134 + 0 + + + + UNKL + 284935 + KRASNOYARSK + + RS + 56.18 + 92.52 + -9999 + 0 + + + + UNLL + 999999 + KOLPASHEVO + NO + RA + 58.30 + 82.90 + 76 + 0 + + + + UNNT + 296340 + NOVOSIBIRSK/TOLMACH + + RA + 55.03 + 82.90 + 177 + 0 + + + + UNOO + 286980 + OMSK + NO + RA + 54.93 + 73.40 + 123 + 0 + + + + UODD + 206740 + DIKSON_ISLAND + DK + RA + 73.53 + 80.40 + 47 + 0 + + + + UOHH + 208910 + KHATANGA + DK + RA + 71.98 + 102.47 + 24 + 0 + + + + UOTT + 234720 + TURUKHANSK + + RA + 65.78 + 087.95 + 37 + 0 + + + + URRV + 273290 + ROSTOV + MS + RS + 57.20 + 39.42 + 102 + 0 + + + + URWA + 999999 + ASTRAKHAN + + RS + 46.35 + 47.97 + -22 + 0 + + + + URWW + 345600 + VOLGOGRAD + TB + RS + 48.68 + 44.35 + 145 + 0 + + + + USCC + 286420 + CHELYABINSK/BALANDI + SV + RA + 55.18 + 61.32 + -9999 + 0 + + + + USDD + 999999 + SALEKHARD + NO + RA + 66.53 + 66.53 + 358 + 0 + + + + USDS + 235520 + TARKO-SALE + NO + RA + 64.92 + 77.82 + 27 + 0 + + + + USHB + 236310 + BEREZOVO + NO + RA + 63.93 + 65.05 + 27 + 0 + + + + USHH + 239330 + HANTY-MANSIJSK + NO + RA + 60.97 + 69.07 + 40 + 0 + + + + USKK + 999999 + KIROV + MS + RS + 58.60 + 49.63 + 158 + 0 + + + + USPP + 282250 + PERM + SV + RA + 58.02 + 56.30 + 172 + 0 + + + + USRR + 238490 + SURGUT + NO + RA + 61.25 + 73.50 + 44 + 0 + + + + USSS + 284400 + SVERDLOVSK + SV + RA + 56.80 + 60.63 + 237 + 0 + + + + USUU + 286610 + KURGAN + SV + RA + 55.47 + 65.40 + 79 + 0 + + + + UTAA + 388800 + ASHABAD + TA + RA + 37.97 + 58.33 + 210 + 0 + + + + UTTT + 384570 + TASHKENT/YUZNI + TA + RA + 41.27 + 69.27 + 489 + 0 + + + + UUWW + 275185 + MOSCOW/VNUKOVO + MS + RS + 55.65 + 37.27 + 203 + 0 + + + + UUYP + 234180 + PECHORA + AR + RS + 65.11 + 57.10 + 61 + 0 + + + + UUYW + 232260 + VORKUTA + AR + RA + 67.48 + 64.02 + 180 + 0 + + + + UUYY + 238040 + SYKTYVKAR + AR + RA + 61.72 + 50.83 + 119 + 0 + + + + UWKD + 275950 + KAZAN' + MS + RS + 55.78 + 49.18 + 116 + 0 + + + + UWUU + 287220 + UFA + SV + RA + 54.75 + 56.00 + 105 + 0 + + + + VABB + 430030 + BOMBAY/SANTA_CR + + IN + 19.12 + 72.84 + 14 + 0 + + + + VCBI + 434500 + COLOMBO/KATUNAYAKE + + SB + 7.17 + 79.88 + 8 + 0 + + + + VGZR + 419220 + KURMITOLA/ZIA_INTL + + BW + 23.85 + 90.40 + 10 + 0 + + + + VHHH + 450070 + HONG_KONG_INTL_ARPT + + HK + 22.33 + 114.18 + 24 + 0 + + + + VIDP + 421810 + INDIRA_GANDHI/DELHI + + IN + 28.57 + 77.12 + 233 + 0 + + + + WSSS + 486980 + SINGAPORE/CHANG + + SR + 1.37 + 103.98 + 16 + 0 + + + + YBRF + 945780 + BRISBANE + QU + AU + -27.43 + 153.08 + 2 + 0 + + + + YBTL + 942940 + TOWNSVILLE(CIV/MIL) + QU + AU + -19.25 + 146.75 + 6 + 0 + + + + YMHF + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YMMB + 948700 + MOORABBIN_AIRPORT + VC + AU + -37.98 + 145.10 + 13 + 0 + + + + YMMC + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YMRF + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YPDM + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YPRF + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YPRM + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + YSRF + 948640 + MELBOURNE + VC + AU + -37.73 + 144.90 + 81 + 0 + + + + ZPPP + 567780 + KUNMING/WUJIABA + CD + CI + 25.02 + 102.68 + 1892 + 0 + + + diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.mcidas/src/gov/noaa/nws/ncep/edex/plugin/mcidas/decoder/McidasDecoder.java b/ncep/gov.noaa.nws.ncep.edex.plugin.mcidas/src/gov/noaa/nws/ncep/edex/plugin/mcidas/decoder/McidasDecoder.java index b1091c6ede..74babfab6e 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.mcidas/src/gov/noaa/nws/ncep/edex/plugin/mcidas/decoder/McidasDecoder.java +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.mcidas/src/gov/noaa/nws/ncep/edex/plugin/mcidas/decoder/McidasDecoder.java @@ -18,6 +18,7 @@ import com.raytheon.edex.plugin.AbstractDecoder; import com.raytheon.uf.common.dataplugin.PluginDataObject; import com.raytheon.uf.common.dataplugin.PluginException; import com.raytheon.uf.common.time.DataTime; +import com.raytheon.uf.edex.decodertools.core.DecoderTools; import com.raytheon.uf.edex.decodertools.time.TimeTools; /** @@ -261,7 +262,10 @@ public class McidasDecoder extends AbstractDecoder { record.setAreaName(areaName); String fileName = ""; if (headers != null) { - fileName = (String) headers.get("traceId"); + // fileName = (String) headers.get("traceId"); + File ingestFile = new File( + (String) headers.get(DecoderTools.INGEST_FILE_NAME)); + fileName = ingestFile.getName(); } record.setInputFileName(fileName); diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.mosaic/utility/edex_static/base/distribution/mosaic.xml b/ncep/gov.noaa.nws.ncep.edex.plugin.mosaic/utility/edex_static/base/distribution/mosaic.xml index 9902240fa0..52b4707d23 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.mosaic/utility/edex_static/base/distribution/mosaic.xml +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.mosaic/utility/edex_static/base/distribution/mosaic.xml @@ -1,4 +1,4 @@ - ^MANUAL INGEST.* + ^MOSAIC.* diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.ncgrib/utility/edex_static/base/grib/ncgrib/ncgribModelNameMap.xml b/ncep/gov.noaa.nws.ncep.edex.plugin.ncgrib/utility/edex_static/base/grib/ncgrib/ncgribModelNameMap.xml index 8eb3e0c393..ebda0cdf06 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.ncgrib/utility/edex_static/base/grib/ncgrib/ncgribModelNameMap.xml +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.ncgrib/utility/edex_static/base/grib/ncgrib/ncgribModelNameMap.xml @@ -2,6 +2,10 @@ + + ecavg_.* + ecensDerv + gens_cmce.*_cmc_geavg.* cmceDerv @@ -219,4 +223,4 @@ sref40Derv - \ No newline at end of file + diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.ncscat/res/spring/ncscat-ingest.xml b/ncep/gov.noaa.nws.ncep.edex.plugin.ncscat/res/spring/ncscat-ingest.xml index a84ff1ac4c..70c0755d38 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.ncscat/res/spring/ncscat-ingest.xml +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.ncscat/res/spring/ncscat-ingest.xml @@ -15,7 +15,7 @@ - + + ^NCSCAT.* diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.ncuair/src/gov/noaa/nws/ncep/edex/plugin/ncuair/util/NcUairParser.java b/ncep/gov.noaa.nws.ncep.edex.plugin.ncuair/src/gov/noaa/nws/ncep/edex/plugin/ncuair/util/NcUairParser.java index 1791515c59..4bd2c33a41 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.ncuair/src/gov/noaa/nws/ncep/edex/plugin/ncuair/util/NcUairParser.java +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.ncuair/src/gov/noaa/nws/ncep/edex/plugin/ncuair/util/NcUairParser.java @@ -12,6 +12,7 @@ * 02/2010 210 T. Lee Fixed TTCC tropopause * 09/2011 Chin Chen add batch parsing methods for better performance * 09/2011 457 S. Gurung Renamed H5 to Nc and h5 to nc + * 12/2013 T. Lee Fixed TTCC Wmax pressure off by factor of 10 * * * @@ -25,1095 +26,1188 @@ package gov.noaa.nws.ncep.edex.plugin.ncuair.util; import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairLiftedIndex; import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairMaxWind; import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairObsLevels; -import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairTropopause; import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairRecord; -import gov.noaa.nws.ncep.edex.plugin.ncuair.util.NcUairWindGroup; +import gov.noaa.nws.ncep.common.dataplugin.ncuair.NcUairTropopause; import gov.noaa.nws.ncep.common.tools.IDecoderConstantsN; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Scanner; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.*; - -import com.raytheon.edex.exception.DecoderException; public class NcUairParser { - //UU is data from ship; XX is data from mobile - public static final int TTAA=1; - public static final int TTBB=2; - public static final int TTCC=3; - public static final int TTDD=4; - public static final int PPAA=5; - public static final int PPBB=6; - public static final int PPCC=7; - public static final int PPDD=8; - public static final int UUAA=1; - public static final int UUBB=2; - public static final int UUCC=3; - public static final int UUDD=4; - public static final int XXAA=1; - public static final int XXBB=2; - public static final int XXCC=9; - public static final int XXDD=10; - - /** + // UU is data from ship; XX is data from mobile + public static final int TTAA = 1; + + public static final int TTBB = 2; + + public static final int TTCC = 3; + + public static final int TTDD = 4; + + public static final int PPAA = 5; + + public static final int PPBB = 6; + + public static final int PPCC = 7; + + public static final int PPDD = 8; + + public static final int UUAA = 1; + + public static final int UUBB = 2; + + public static final int UUCC = 3; + + public static final int UUDD = 4; + + public static final int XXAA = 1; + + public static final int XXBB = 2; + + public static final int XXCC = 9; + + public static final int XXDD = 10; + + /** * Constructor */ public NcUairParser() { } - + /** * Return the dataType with format "(TT|PP|XX|UU)(AA|BB|CC|DD)" * - * @param theReport The input upper air data report + * @param theReport + * The input upper air data report * @return */ public static String getDataType(String theReport) { - - /** Regular expression for dataType */ - final String DATA_TYPE = "((TT|PP|XX|UU)(AA|BB|CC|DD)) "; - Pattern datatypePattern = Pattern.compile(DATA_TYPE); - Matcher datatypeMatcher = datatypePattern.matcher(theReport); - String retType = null; - - if ( datatypeMatcher.find()) { - retType = datatypeMatcher.group(1); - } - return retType; + + /** Regular expression for dataType */ + final String DATA_TYPE = "((TT|PP|XX|UU)(AA|BB|CC|DD)) "; + Pattern datatypePattern = Pattern.compile(DATA_TYPE); + Matcher datatypeMatcher = datatypePattern.matcher(theReport); + String retType = null; + + if (datatypeMatcher.find()) { + retType = datatypeMatcher.group(1); + } + return retType; } - + /** * Return the station Number * - * @param theReport The input upper air data report + * @param theReport + * The input upper air data report * @return */ public static String getStationNumber(String theReport) { - /** Regular expression for stationNumber */ - final String STATIONNUMBER = "(TT|PP)(AA|BB|CC|DD) ( )?(\\d{2})(\\d{2})(\\d{1}|/) (\\d{5})"; - Pattern stationNumberPattern = Pattern.compile(STATIONNUMBER); - Matcher stationNumberMatcher = stationNumberPattern.matcher(theReport); - String retStationNumber = null; + /** Regular expression for stationNumber */ + final String STATIONNUMBER = "(TT|PP)(AA|BB|CC|DD) ( )?(\\d{2})(\\d{2})(\\d{1}|/) (\\d{5})"; + Pattern stationNumberPattern = Pattern.compile(STATIONNUMBER); + Matcher stationNumberMatcher = stationNumberPattern.matcher(theReport); + String retStationNumber = null; - if ( stationNumberMatcher.find()) { - retStationNumber = stationNumberMatcher.group(7); - } else { - /** Regular expression for stationNumber */ - final String STATIONNUMBER2 = "(TT|PP)(AA|BB|CC|DD) (\\d{5}) NIL"; - Pattern stationNumberPattern2 = Pattern.compile(STATIONNUMBER2); - Matcher stationNumberMatcher2 = stationNumberPattern2.matcher(theReport); - if ( stationNumberMatcher2.find()) { - retStationNumber = stationNumberMatcher2.group(3); - } else { - /** Regular expression for stationNumber */ - final String STATIONNUMBER3 = "(TT|PP)(AA|BB|CC|DD) (/////) (\\d{5}) NIL"; - Pattern stationNumberPattern3 = Pattern.compile(STATIONNUMBER3); - Matcher stationNumberMatcher3 = stationNumberPattern3.matcher(theReport); - if ( stationNumberMatcher3.find()) { - retStationNumber = stationNumberMatcher3.group(4); - } - } - } - return retStationNumber; + if (stationNumberMatcher.find()) { + retStationNumber = stationNumberMatcher.group(7); + } else { + /** Regular expression for stationNumber */ + final String STATIONNUMBER2 = "(TT|PP)(AA|BB|CC|DD) (\\d{5}) NIL"; + Pattern stationNumberPattern2 = Pattern.compile(STATIONNUMBER2); + Matcher stationNumberMatcher2 = stationNumberPattern2 + .matcher(theReport); + if (stationNumberMatcher2.find()) { + retStationNumber = stationNumberMatcher2.group(3); + } else { + /** Regular expression for stationNumber */ + final String STATIONNUMBER3 = "(TT|PP)(AA|BB|CC|DD) (/////) (\\d{5}) NIL"; + Pattern stationNumberPattern3 = Pattern.compile(STATIONNUMBER3); + Matcher stationNumberMatcher3 = stationNumberPattern3 + .matcher(theReport); + if (stationNumberMatcher3.find()) { + retStationNumber = stationNumberMatcher3.group(4); + } + } + } + return retStationNumber; } - - /** - * Get the correction indicator from WMO header. - * - * @param theReport The input upper air data report - * @return a String for corIndicator - */ - public static String findCorIndicator(String theReport) { - - String corIndicator = null; - - /** Regular expression for corIndicator */ - final String corpat = "[A-Z]{4}\\d{0,2} [A-Z]{4} \\d{6}( )?([A-Z]{3})?\\r\\r\\n"; - Pattern CORPattern = Pattern.compile(corpat); - Matcher corMatcher = CORPattern.matcher(theReport); - if ( corMatcher.find()) { - corIndicator = corMatcher.group(2); - } - - return corIndicator; - } - - /** - * From a given dataType returns an integer to represent - * that dataType. - * - * @param dataType The input dataType - * @return an integer for that dataType - */ - public static Integer getUairType(String dataType) { - HashMap uairhm = new HashMap(); - - uairhm.put("TTAA", new Integer(TTAA)); - uairhm.put("TTBB", new Integer(TTBB)); - uairhm.put("TTCC", new Integer(TTCC)); - uairhm.put("TTDD", new Integer(TTDD)); - - uairhm.put("PPAA", new Integer(PPAA)); - uairhm.put("PPBB", new Integer(PPBB)); - uairhm.put("PPCC", new Integer(PPCC)); - uairhm.put("PPDD", new Integer(PPDD)); - - uairhm.put("XXAA", new Integer(XXAA)); - uairhm.put("XXBB", new Integer(XXBB)); - uairhm.put("XXCC", new Integer(XXCC)); - uairhm.put("XXDD", new Integer(XXDD)); - - uairhm.put("UUAA", new Integer(UUAA)); - uairhm.put("UUBB", new Integer(UUBB)); - uairhm.put("UUCC", new Integer(UUCC)); - uairhm.put("UUDD", new Integer(UUDD)); - - return (Integer) uairhm.get(dataType); - } - - /** - * Parse the entire code message and interpret to - * observation level information according its data - * type. + /** + * Get the correction indicator from WMO header. * - * @param codeMessage The input upper air code groups - * @param record The in and out Uair record + * @param theReport + * The input upper air data report + * @return a String for corIndicator + */ + public static String findCorIndicator(String theReport) { + + String corIndicator = null; + + /** Regular expression for corIndicator */ + final String corpat = "[A-Z]{4}\\d{0,2} [A-Z]{4} \\d{6}( )?([A-Z]{3})?\\r\\r\\n"; + Pattern CORPattern = Pattern.compile(corpat); + Matcher corMatcher = CORPattern.matcher(theReport); + + if (corMatcher.find()) { + corIndicator = corMatcher.group(2); + } + + return corIndicator; + } + + /** + * From a given dataType returns an integer to represent that dataType. + * + * @param dataType + * The input dataType + * @return an integer for that dataType + */ + public static Integer getUairType(String dataType) { + HashMap uairhm = new HashMap(); + + uairhm.put("TTAA", new Integer(TTAA)); + uairhm.put("TTBB", new Integer(TTBB)); + uairhm.put("TTCC", new Integer(TTCC)); + uairhm.put("TTDD", new Integer(TTDD)); + + uairhm.put("PPAA", new Integer(PPAA)); + uairhm.put("PPBB", new Integer(PPBB)); + uairhm.put("PPCC", new Integer(PPCC)); + uairhm.put("PPDD", new Integer(PPDD)); + + uairhm.put("XXAA", new Integer(XXAA)); + uairhm.put("XXBB", new Integer(XXBB)); + uairhm.put("XXCC", new Integer(XXCC)); + uairhm.put("XXDD", new Integer(XXDD)); + + uairhm.put("UUAA", new Integer(UUAA)); + uairhm.put("UUBB", new Integer(UUBB)); + uairhm.put("UUCC", new Integer(UUCC)); + uairhm.put("UUDD", new Integer(UUDD)); + + return (Integer) uairhm.get(dataType); + } + + /** + * Parse the entire code message and interpret to observation level + * information according its data type. + * + * @param codeMessage + * The input upper air code groups + * @param record + * The in and out Uair record * @return */ - static int exceptionCount=0; - public static void getLevels(String codeMessage, NcUairRecord record) { + static int exceptionCount = 0; - Boolean windKnot = false; + public static void getLevels(String codeMessage, NcUairRecord record) { - String dataType = record.getDataType(); + Boolean windKnot = false; - int uairType = getUairType(dataType); - //System.out.println("getLEvel uair datatype="+dataType); - String[] codeGroupAr;//Chin, do not use fix array size, it will cause exception when size is over 500 = new String[500]; - List codeGpList = new ArrayList(); - //System.out.println("codeMessage...\n" + codeMessage); + String dataType = record.getDataType(); - if ( dataType.substring(0,2).equals("XX") || dataType.substring(0,2).equals("UU") ) { - windKnot = NcUairShipMobile.getWindKnot(); - } else { - windKnot = NcUairTimeGroup.getWindKnot(); - } - // Break the code message into segments by a " " - Scanner sc = new Scanner(codeMessage).useDelimiter(" "); - int cgSize = 0; - - while (sc.hasNext()) { - String codeGroup = sc.next(); - codeGpList.add(codeGroup); - //Chin codeGroupAr[cgSize] = codeGroup; - cgSize++; - } - codeGroupAr = new String[cgSize]; - codeGpList.toArray(codeGroupAr); - try{ - switch (uairType) { - case TTAA : { - processTTAACC(codeGroupAr, cgSize, windKnot, record); - break; - } - case TTBB : { - processTTBBDD(codeGroupAr, cgSize, windKnot, record); - break; - } - case TTCC : { - processTTAACC(codeGroupAr, cgSize, windKnot, record); - break; - } - case TTDD : { - processTTBBDD(codeGroupAr, cgSize, windKnot, record); - break; - } - case PPAA: { - processPPAACC(codeGroupAr, cgSize, windKnot, record); - break; - } - case PPBB : { - processPPBBDD(codeGroupAr, cgSize, windKnot, record); - break; - } - case PPCC : { - processPPAACC(codeGroupAr, cgSize, windKnot, record); - break; - } - case PPDD : { - processPPBBDD(codeGroupAr, cgSize, windKnot, record); - break; - } - default: { - System.out.println("\n Invalid datatype!"); - } - } - } catch ( Exception e){ - exceptionCount++; - System.out.println("NcUair parsing exception! #"+exceptionCount); - } - } + int uairType = getUairType(dataType); + // System.out.println("getLEvel uair datatype="+dataType); + String[] codeGroupAr;// Chin, do not use fix array size, it will cause + // exception when size is over 500 = new + // String[500]; + List codeGpList = new ArrayList(); + // System.out.println("codeMessage...\n" + codeMessage); - - /** - * Decodes a Pres/height, temperature, and wind fields in the - * forms PPhhh and TTTdd and DDDff for TTAA/CC.' - * - * @param codeGroupAr The input uppair code group array - * @param cgSize The input cgSize is the size of the code group array - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processTTAACC(String[] codeGroupAr, int cgSize, - Boolean windKnot, NcUairRecord record) { - - Boolean above= false; - Boolean endrpt = false; - Boolean topwindFlag = false; - Boolean drop = false; - - int level = 0; - int topwind = IDecoderConstantsN.UAIR_INTEGER_MISSING; - - String dataType = record.getDataType(); - String stationNumber = record.getStnum(); + if (dataType.substring(0, 2).equals("XX") + || dataType.substring(0, 2).equals("UU")) { + windKnot = NcUairShipMobile.getWindKnot(); + } else { + windKnot = NcUairTimeGroup.getWindKnot(); + } + // Break the code message into segments by a " " + Scanner sc = new Scanner(codeMessage).useDelimiter(" "); + int cgSize = 0; - if ( dataType.substring(2,4).equals("CC") ) { - above = true; - } - if ( dataType.substring(0,2).equals("XX") || dataType.substring(0,2).equals("UU") ) { - topwind = NcUairShipMobile.getTopwind(); - if ( dataType.substring(0,2).equals("XX") ) { - drop = true; - } - } else { - topwind = NcUairTimeGroup.getTopwind(); - } - if ( topwind == IDecoderConstantsN.UAIR_INTEGER_MISSING ) { - topwindFlag = true; - } - int topWind = getTopWind( topwind, above); - - int i = 0; - while ( i < cgSize && ! endrpt ) { - - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - - if ( codeGroupAr[i].length() < 3) { - break; - } - if ( codeGroupAr[i].equals("51515") ) { - endrpt = true; - i++; - // Decode lifted index and mean low level wind groups - if ( (i + 2) < cgSize ) { - processLiftedIndex(codeGroupAr, i, windKnot, record); - } - break; - } - - // This should be drop data - if ( codeGroupAr[i].equals("61616") ) { - endrpt = true; - if ( drop ) { - // process drop data - //System.out.println("got drop 61616 in TT AACC - need to process...\n"); - } - break; - } - - String pp = codeGroupAr[i].substring(0,2); - if ( pp.equals("^C") ) { - break; - } - - if ( pp.equals("88") || pp.equals("77") || pp.equals("66") ) { - // Decode supplemental data - String hhh = codeGroupAr[i].substring(2,5); - if ( pp.equals("88") ) { - // Decode tropopause information - if ( hhh.equals("999")) { - // NO tropopause data - i++; - } else { - processTropopause(codeGroupAr, i, topwindFlag, windKnot, record, above); - if ( topwindFlag ) { - i = i + 2; - } else { - i = i + 3; - } - } - } else if ( pp.equals("77") || pp.equals("66")) { - // Decode maximum wind group - if ( hhh.equals("999")) { - // NO maximum wind data - i++; - } else { - processMaximumWindShear(codeGroupAr, i, cgSize, windKnot, record); - i = i + 3; - } - } - } else if ( codeGroupAr[i].equals("31313") ) { - i = i + 3; - } else if ( codeGroupAr[i].equals("41414") ) { - i = i + 2; - } else { - int range = i + 2; - if ( range < cgSize ) { - // Decode TTAA normal report -pressure and height - NcUairPressureHeightGroup.PressureHeightField(codeGroupAr[i], above, level, stationNumber, dataType, record); - pres = NcUairPressureHeightGroup.getPressure(); - height = NcUairPressureHeightGroup.getHeight(); + while (sc.hasNext()) { + String codeGroup = sc.next(); + codeGpList.add(codeGroup); + // Chin codeGroupAr[cgSize] = codeGroup; + cgSize++; + } + codeGroupAr = new String[cgSize]; + codeGpList.toArray(codeGroupAr); + try { + switch (uairType) { + case TTAA: { + processTTAACC(codeGroupAr, cgSize, windKnot, record); + break; + } + case TTBB: { + processTTBBDD(codeGroupAr, cgSize, windKnot, record); + break; + } + case TTCC: { + processTTAACC(codeGroupAr, cgSize, windKnot, record); + break; + } + case TTDD: { + processTTBBDD(codeGroupAr, cgSize, windKnot, record); + break; + } + case PPAA: { + processPPAACC(codeGroupAr, cgSize, windKnot, record); + break; + } + case PPBB: { + processPPBBDD(codeGroupAr, cgSize, windKnot, record); + break; + } + case PPCC: { + processPPAACC(codeGroupAr, cgSize, windKnot, record); + break; + } + case PPDD: { + processPPBBDD(codeGroupAr, cgSize, windKnot, record); + break; + } + default: { + System.out.println("\n Invalid datatype!"); + } + } + } catch (Exception e) { + exceptionCount++; + System.out.println("NcUair parsing exception! #" + exceptionCount); + } + } - // Decode temperature group - NcUairTempGroup.TempField(codeGroupAr[i+1]); - temp = NcUairTempGroup.getTemperature(); - dewpTemp = NcUairTempGroup.getDewpointTemp(); + /** + * Decodes a Pres/height, temperature, and wind fields in the forms PPhhh + * and TTTdd and DDDff for TTAA/CC.' + * + * @param codeGroupAr + * The input uppair code group array + * @param cgSize + * The input cgSize is the size of the code group array + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processTTAACC(String[] codeGroupAr, int cgSize, + Boolean windKnot, NcUairRecord record) { - if ( pres >= topWind ) { - // Decode wind group - NcUairWindGroup.WindField(codeGroupAr[i+2], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - if ( wdir > 360 ) { - wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - endrpt = true; - break; - } - } else { - i--; - } - - // Add level - addLevels(record, pres, height, temp, dewpTemp, wdir, wspeed); - } - i = i + 3; - level++; - } - } - - } - - /** - * Decodes a pressure/height, temperature, and wind fields in the - * forms PPhhh and TTTdd and DDDff for TTBB/DD. - * - * @param codeGroupAr The input uppair code group array - * @param cgSize The input cgSize is the size of the code group array - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processTTBBDD(String[] codeGroupAr, - int cgSize, Boolean windKnot, NcUairRecord record) { - - Boolean above=false; - Boolean endrpt = false; - Boolean wind = false; - Boolean drop = false; - - String dataType = record.getDataType(); - - if ( dataType.substring(2,4).equals("DD") ) { - above = true; - } - if ( dataType.substring(0,2).equals("XX") ) { - drop = true; - } + Boolean above = false; + Boolean endrpt = false; + Boolean topwindFlag = false; + Boolean drop = false; - int i = 0; - while ( i < cgSize && ! endrpt ) { - - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - - if ( codeGroupAr[i].equals("51515") ) { - endrpt = true; - i++; - // Decode lifted index and mean low level wind groups - if ( (i + 2) < cgSize ) { - processLiftedIndex(codeGroupAr, i, windKnot, record); - } - break; - } - - if ( codeGroupAr[i].equals("61616") ) { - endrpt = true; - if ( drop ) { - //System.out.println("got drop 61616 in BBDD need to process.... \n"); - } - break; - } - - if ( codeGroupAr[i].equals("21212") ) { - wind = true; - i++; - //System.out.println("got 21212 significant wind group need to process...\n"); - break; - } - - if ( codeGroupAr[i].equals("31313")) { - i = i + 3; - } else if ( codeGroupAr[i].equals("41414") ) { - i = i + 2; - } else { - int range = i + 1; - if ( range < cgSize ) { - // Decode Pres group - pres = getPressureFromTTBBDD(codeGroupAr[i], above); - - if ( ! wind ) { - // Decode temperature group - NcUairTempGroup.TempField(codeGroupAr[i+1]); - temp = NcUairTempGroup.getTemperature(); - dewpTemp = NcUairTempGroup.getDewpointTemp(); - } else { - // Decode wind group - NcUairWindGroup.WindField(codeGroupAr[i+1], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - } - - // Add level - addLevels(record, pres, height, temp, dewpTemp, wdir, wspeed); - - } - i = i + 2; - } - } - } - - /** - * Decodes a pressure/height and temperature fields for PPAACC. - * These reports contain wind data at mandatory - * levels below 100 mb. - * - * @param codeGroupAr The input uppair code group array - * @param cgSize The input cgSize is the size of the code group array - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processPPAACC(String[] codeGroupAr, int cgSize, - Boolean windKnot, NcUairRecord record) { + int level = 0; + int topwind = IDecoderConstantsN.UAIR_INTEGER_MISSING; - Boolean above=false; - Boolean endrpt = false; - float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - - float pold; - - String dataType = record.getDataType(); - - if ( dataType.substring(2,4).equals("CC") ) { - above = true; - pold = 100; - } else { - pold = 2000; - } + String dataType = record.getDataType(); + String stationNumber = record.getStnum(); - int i = 0; - while ( i < cgSize && ! endrpt ) { - - if ( codeGroupAr[i].equals("51515")) { - endrpt = true; - break; - } - - String ppIndicator = codeGroupAr[i].substring(0,2); - // Check that first two characters are 44 or 55. - if ( ppIndicator.equals("44") || ppIndicator.equals("55")) { - String skip = codeGroupAr[i].substring(2,5); - if ( skip.equals("///")) { - i++; - } else { - // Decode number of pressures which is the third character. - /* chin, fix bug, when a non-number char is decoded */ - int onetothree=0; - try{ - onetothree = Integer.parseInt(codeGroupAr[i].substring(2,3)); - } catch (NumberFormatException e) { - onetothree=0; - - } - if ( onetothree > 0 && onetothree <= 3) { - // Decode the pressure - float[] presArr = { IDecoderConstantsN.UAIR_FLOAT_MISSING, - IDecoderConstantsN.UAIR_FLOAT_MISSING, - IDecoderConstantsN.UAIR_FLOAT_MISSING, - }; - presArr = getPressureFromPPAACC(codeGroupAr[i], above); - - i++; - for (int index = 0; index < onetothree; index++) { - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float ppp = presArr[index]; - if ( ppp < pold ) { - pres = presArr[index]; - pold = ppp; - } - - // Decode wind group - if ( i < cgSize ) { - NcUairWindGroup.WindField(codeGroupAr[i], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - } - - // Add level - addLevels(record, pres, height, temp, dewpTemp, wdir, wspeed); - - i++; - } - } - else { - // no pressure group - i++; - } - } - } else if ( ppIndicator.equals("77") || ppIndicator.equals("66")) { - // Decode maximum wind group - String hhh = codeGroupAr[i].substring(2,5); - if ( hhh.equals("999")) { - // NO maximum wind and wind shear data - i++; - } else { - processMaximumWindShear(codeGroupAr, i, cgSize, windKnot, record); - i = i + 3; - } - } else { - i++; - break; - } - } - } + if (dataType.substring(2, 4).equals("CC")) { + above = true; + } + if (dataType.substring(0, 2).equals("XX") + || dataType.substring(0, 2).equals("UU")) { + topwind = NcUairShipMobile.getTopwind(); + if (dataType.substring(0, 2).equals("XX")) { + drop = true; + } + } else { + topwind = NcUairTimeGroup.getTopwind(); + } + if (topwind == IDecoderConstantsN.UAIR_INTEGER_MISSING) { + topwindFlag = true; + } + int topWind = getTopWind(topwind, above); - /** - * Decodes a pressure/height and temperature fields for PPBBDD. - * These reports contain significant wind data above 100 mb. - * - * @param codeGroupAr The input uppair code group array - * @param cgSize The input cgSize is the size of the code group array - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processPPBBDD(String[] codeGroupAr, int cgSize, - Boolean windKnot, NcUairRecord record) { - - Boolean endrpt = false; - Boolean pressflag = false; - Boolean above = false; - - String dataType = record.getDataType(); - - if ( dataType.substring(2,4).equals("DD") ) { - above = true; - } + int i = 0; + while (i < cgSize && !endrpt) { - int i = 0; - try{ - while ( i < cgSize && ! endrpt ) { - - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - - - if ( codeGroupAr[i].equals("21212") ) { - pressflag = true; - i++; - if ( i >= cgSize ) { - break; - } - //System.out.println("got 21212 in PP BBDD - need to process...\n"); - break; - } - - if ( codeGroupAr[i].equals("51515") || codeGroupAr[i].equals("41414")) { - endrpt = true; - //System.out.println("got end group 51515 41414 in PP BBDD - BREAK \n"); - break; - } + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - // Convert the unit digits to integers and compute height. - Boolean missing = false; - int noWind = 0; - if ( ! pressflag ) { - while ( ! missing && ( noWind < 3 ) ) { - int index = noWind + 3; - String unit="/"; - unit = codeGroupAr[i].substring(index-1,index); - - if ( unit.equals(("/")) ) { - missing = true; - break; - } + if (codeGroupAr[i].length() < 3) { + break; + } + if (codeGroupAr[i].equals("51515")) { + endrpt = true; + i++; + // Decode lifted index and mean low level wind groups + if ((i + 2) < cgSize) { + processLiftedIndex(codeGroupAr, i, windKnot, record); + } + break; + } - // Decode height; invalid height if returns FLOAT_MISSING. - height = getHeightFromPPBBDD(codeGroupAr[i], index); - if ( height == IDecoderConstantsN.UAIR_FLOAT_MISSING ) { - i++; - break; - } - int range = i+noWind+1; - if ( range < cgSize ) { - // Decode wind group. - NcUairWindGroup.WindField(codeGroupAr[i+noWind+1], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - } - - // Add level - addLevels(record, pres, height, temp, dewpTemp, wdir, wspeed); - - noWind ++; - } + // This should be drop data + if (codeGroupAr[i].equals("61616")) { + endrpt = true; + if (drop) { + // process drop data + // System.out.println("got drop 61616 in TT AACC - need to process...\n"); + } + break; + } - i = i + noWind + 1; - } else { - int range = i+1; - if ( range < cgSize ) { - // Decode pressure group - pres = getPressureFromTTBBDD(codeGroupAr[i], above); - NcUairWindGroup.WindField(codeGroupAr[i+1], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - } - i= i+2; - } - } - } - catch(Exception e) { - - } - } - - /** - * Decodes a pressure field for mandatory wind - * reports (PPAA or PPCC). The pressures reporting are returned. - * - * @param presGroup The input pressure code group - * @param above The input above is flag - * @return pressure - */ - public static float[] getPressureFromPPAACC(String presGroup, Boolean above) { + String pp = codeGroupAr[i].substring(0, 2); + if (pp.equals("^C")) { + break; + } - float pressure[] = { IDecoderConstantsN.UAIR_FLOAT_MISSING, - IDecoderConstantsN.UAIR_FLOAT_MISSING, - IDecoderConstantsN.UAIR_FLOAT_MISSING - }; + if (pp.equals("88") || pp.equals("77") || pp.equals("66")) { + // Decode supplemental data + String hhh = codeGroupAr[i].substring(2, 5); + if (pp.equals("88")) { + // Decode tropopause information + if (hhh.equals("999")) { + // NO tropopause data + i++; + } else { + processTropopause(codeGroupAr, i, topwindFlag, + windKnot, record, above); + if (topwindFlag) { + i = i + 2; + } else { + i = i + 3; + } + } + } else if (pp.equals("77") || pp.equals("66")) { + // Decode maximum wind group + if (hhh.equals("999")) { + // NO maximum wind data + i++; + } else { + processMaximumWindShear(codeGroupAr, i, cgSize, + windKnot, above, record); + i = i + 3; + } + } + } else if (codeGroupAr[i].equals("31313")) { + i = i + 3; + } else if (codeGroupAr[i].equals("41414")) { + i = i + 2; + } else { + int range = i + 2; + if (range < cgSize) { + // Decode TTAA normal report -pressure and height + NcUairPressureHeightGroup.PressureHeightField( + codeGroupAr[i], above, level, stationNumber, + dataType, record); + pres = NcUairPressureHeightGroup.getPressure(); + height = NcUairPressureHeightGroup.getHeight(); - String bpres[] = { "00", "92", "85", "70", "50", "40", - "30", "25", "20", "15", "10" }; + // Decode temperature group + NcUairTempGroup.TempField(codeGroupAr[i + 1]); + temp = NcUairTempGroup.getTemperature(); + dewpTemp = NcUairTempGroup.getDewpointTemp(); - String apres[] = { "70", "50", "30", "20", "10", - "07", "05", "03", "02", "01" }; + if (pres >= topWind) { + // Decode wind group + NcUairWindGroup.WindField(codeGroupAr[i + 2], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + if (wdir > 360) { + wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + endrpt = true; + break; + } + } else { + i--; + } - int rbprs[] = { 1000, 925, 850, 700, 500, 400, - 300, 250, 200, 150, 100 }; + // Add level + addLevels(record, pres, height, temp, dewpTemp, wdir, + wspeed); + } + i = i + 3; + level++; + } + } - int raprs[] = { 70, 50, 30, 20, 10, 7, 5, 3, 2, 1 }; + } - int level = -1; + /** + * Decodes a pressure/height, temperature, and wind fields in the forms + * PPhhh and TTTdd and DDDff for TTBB/DD. + * + * @param codeGroupAr + * The input uppair code group array + * @param cgSize + * The input cgSize is the size of the code group array + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processTTBBDD(String[] codeGroupAr, int cgSize, + Boolean windKnot, NcUairRecord record) { - String hh = presGroup.substring(3,5); - // Check first pressure against list. - int ifirst = -1; - if ( ! above ) { - for ( String plevel : bpres ) { - ifirst ++; - if ( hh.equals(plevel)) { - level = ifirst; - break; - } - } - } else { - for ( String plevel : apres ) { - ifirst ++; - if ( hh.equals(plevel)) { - level = ifirst; - break; - } - } - } + Boolean above = false; + Boolean endrpt = false; + Boolean wind = false; + Boolean drop = false; - if ( ! above ) { - if ( ifirst < 11 && level >= 0 ) { - pressure[0] = rbprs[level]; - if ( level+1 < 11 ) { - pressure[1] = rbprs[level+1]; - } - if ( level+2 < 11 ) { - pressure[2] = rbprs[level+2]; - } - } - } else { - if ( ifirst < 10 && level >= 0 ) { - pressure[0] = raprs[level]; - if ( level+1 < 10 ) { - pressure[1] = raprs[level+1]; - } - if ( level+2 < 10 ) { - pressure[2] = raprs[level+2]; - } - } - } + String dataType = record.getDataType(); - return pressure; - } + if (dataType.substring(2, 4).equals("DD")) { + above = true; + } + if (dataType.substring(0, 2).equals("XX")) { + drop = true; + } - /** - * Decodes the height from a significant wind field from reports (PPBB or PPDD). - * The field is of the form atuuu where a is 1 if the height is - * above 100000 feet and 9 or 8 otherwise. t is the ten-thousands - * digit of the height and the u's are up to three thousands-of-feet - * fields. The heights are converted from feet to meters. - * - * @param heightGroup The input heightGroup contains the height code group. - * @param index The input index is the current position in the code group. - * @return pressure - */ - public static float getHeightFromPPBBDD(String heightGroup, int index) { - float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; - final double feetToMeter = 0.3048; - - String hhIndicator = heightGroup.substring(0,1); - int iadd = 0; - // Check that the first character of the field is '9' or '1'. - if ( hhIndicator.equals("9") || hhIndicator.equals("8")) { - iadd = 0; - } else { - return height; - } - - // Convert the tens digit to an integer. - int iten; - iten = Integer.parseInt(heightGroup.substring(1,2)) * 10; - int iunit = Integer.parseInt(heightGroup.substring(index-1,index)); - height = ( iten + iunit ) * 1000 + iadd; - height = (float) (height * feetToMeter); - return height; - } - - /** - * Decodes a pressure field from a group reports (TTBB or TTDD). - * The pressures reporting are returned. - * - * @param presGroup The input pressure code group - * @param above The input above is flag - * @return pressure - */ - public static float getPressureFromTTBBDD(String presGroup, Boolean above) { + int i = 0; + while (i < cgSize && !endrpt) { - float pressure = IDecoderConstantsN.UAIR_FLOAT_MISSING; - - String clev[] = { "00", "11", "22", "33", "44", - "55", "66", "77", "88", "99" }; - - if ( presGroup.length() == 5 ) { - String pp = presGroup.substring(0,2); - String hhh = presGroup.substring(2,5); - - int level = -1; - if ( ! pp.equals("//") && ! hhh.substring(2,3).equals("/")) { - for ( String plevel : clev ) { - level++; - if ( pp.equals(plevel)) { - break; - } - } - // If a level was found, decode the pressure. - if ( level != -1 && level < 10 ) { - int ihhh = Integer.parseInt(hhh); - if ( above ) { - pressure = (float) ihhh/ (float) 10; - } else { - if ( ihhh < 100 ) { - pressure = ihhh + 1000; - } else { - pressure = ihhh; - } - } - } - } - } - return pressure; - } + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - /** - * Decodes data for the tropopause level from the TTAA reports. - * The output data are ordered PRES TEMP DWPT DRCT SPED. - * - * @param codeGroupAr The input uppair code group array - * @param index The input index is the current position of the code group array - * @param topwindFlag the input index is the flag to indicate top wind - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processTropopause(String[] codeGroupAr, int index, - Boolean topwindFlag, Boolean windKnot, NcUairRecord record, Boolean above) { - - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; + if (codeGroupAr[i].equals("51515")) { + endrpt = true; + i++; + // Decode lifted index and mean low level wind groups + if ((i + 2) < cgSize) { + processLiftedIndex(codeGroupAr, i, windKnot, record); + } + break; + } - int i = index; + if (codeGroupAr[i].equals("61616")) { + endrpt = true; + if (drop) { + // System.out.println("got drop 61616 in BBDD need to process.... \n"); + } + break; + } - String pp = codeGroupAr[i].substring(0,2); - String hhh = codeGroupAr[i].substring(2,5); + if (codeGroupAr[i].equals("21212")) { + wind = true; + i++; + // System.out.println("got 21212 significant wind group need to process...\n"); + break; + } - if ( pp.equals("88") ) { - // Decode pressure and height - pres = Integer.parseInt(hhh); - if (above) { - pres = pres / 10.f; - } + if (codeGroupAr[i].equals("31313")) { + i = i + 3; + } else if (codeGroupAr[i].equals("41414")) { + i = i + 2; + } else { + int range = i + 1; + if (range < cgSize) { + // Decode Pres group + pres = getPressureFromTTBBDD(codeGroupAr[i], above); - // Decode temperature group - NcUairTempGroup.TempField(codeGroupAr[i+1]); - temp = NcUairTempGroup.getTemperature(); - dewpTemp = NcUairTempGroup.getDewpointTemp(); + if (!wind) { + // Decode temperature group + NcUairTempGroup.TempField(codeGroupAr[i + 1]); + temp = NcUairTempGroup.getTemperature(); + dewpTemp = NcUairTempGroup.getDewpointTemp(); + } else { + // Decode wind group + NcUairWindGroup.WindField(codeGroupAr[i + 1], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + } - if ( ! topwindFlag ) { - // Decode wind group - NcUairWindGroup.WindField(codeGroupAr[i+2], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - } - - addTrop(record, pres, temp, dewpTemp, wdir, wspeed); - } - } - - /** - * Decodes data for the maximum wind level(s) and wind shear from the TTAA reports. - * The output data are ordered PRES DRCT SPED. - * - * @param codeGroupAr The input uppair code group array - * @param index The input index is the current position of the code group array - * @param cgSize the input cgSize is the size of the codeGroupAr. - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processMaximumWindShear(String[] codeGroupAr, - int index, int cgSize, Boolean windKnot, NcUairRecord record) { - - float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float loShear = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float hiShear = IDecoderConstantsN.UAIR_FLOAT_MISSING; + // Add level + addLevels(record, pres, height, temp, dewpTemp, wdir, + wspeed); - int i = index; + } + i = i + 2; + } + } + } - String ppIndicator = codeGroupAr[i].substring(0,2); - String hhh = codeGroupAr[i].substring(2,5); + /** + * Decodes a pressure/height and temperature fields for PPAACC. These + * reports contain wind data at mandatory levels below 100 mb. + * + * @param codeGroupAr + * The input uppair code group array + * @param cgSize + * The input cgSize is the size of the code group array + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processPPAACC(String[] codeGroupAr, int cgSize, + Boolean windKnot, NcUairRecord record) { - if ( ppIndicator.equals("77") || ppIndicator.equals("66")) { - // Decode pressure and height - if ( ! hhh.substring(1,3).equals("//") ) { - pres = Integer.parseInt(hhh); - } - - // Decode wind group - NcUairWindGroup.WindField(codeGroupAr[i+1], windKnot); - wspeed = NcUairWindGroup.getSped(); - wdir = NcUairWindGroup.getDrct(); - - if ( i+2 < cgSize ) { - String windshearIndicator = codeGroupAr[i+2].substring(0,1); - if ( windshearIndicator.equals("4")) { - String below = codeGroupAr[i+2].substring(1,3); - String abov = codeGroupAr[i+2].substring(3,5); - if ( ! below.equals("//")) { - loShear = Integer.parseInt(below); - } - if ( ! abov.equals("//")) { - hiShear = Integer.parseInt(abov); - } - } - } - - NcUairMaxWind mwind = null; - mwind = new NcUairMaxWind(); - mwind.setPres(pres); - mwind.setDrct(wdir); - mwind.setSped(wspeed); - mwind.setLoShear(loShear); - mwind.setHiShear(hiShear); - - record.addMaxWind(mwind); - } - } - - /** - * Decodes data for the lifted index group from the TTAA reports. - * The output data are ordered liTemp, wdirSurface, wspeedSurface, - * wdirAbove, wspeedAbove - * - * @param codeGroupAr The input uppair code group array - * @param index The input index is the current position of the code group array - * @param windKnot The input windKnot is flag to indicate wind in knot or not. - * @param record The in and out Uair record - * @return - */ - public static void processLiftedIndex(String[] codeGroupAr, - int index, Boolean windKnot, NcUairRecord record) { - - float liTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdirSurface = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeedSurface = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wdirAbove = IDecoderConstantsN.UAIR_FLOAT_MISSING; - float wspeedAbove = IDecoderConstantsN.UAIR_FLOAT_MISSING; + Boolean above = false; + Boolean endrpt = false; + float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; - int i = index; - if ( codeGroupAr[i].equals("10164") ) { - int ittt = Integer.parseInt(codeGroupAr[i+1]); - if ( ittt > 50 ) { - // subtract 50 if lllll greater than 50 - ittt = ittt - 50; - } - int isign = ittt%2; - /* - * If the integer is even, the liTemp is positive. - * Otherwise, the liTemp is negative. - */ - if ( isign == 1 ) { - liTemp = - ittt; - } else { - liTemp = ittt; - } - liTemp = (float) (liTemp / 10.); - i = i + 2; - } - - if ( codeGroupAr[i].equals("10194") ) { - // Decode wind group from surface to 5000ft - NcUairWindGroup.WindField(codeGroupAr[i+1], windKnot); - wspeedSurface = NcUairWindGroup.getSped(); - wdirSurface = NcUairWindGroup.getDrct(); + float pold; + + String dataType = record.getDataType(); + + if (dataType.substring(2, 4).equals("CC")) { + above = true; + pold = 100; + } else { + pold = 2000; + } + + int i = 0; + while (i < cgSize && !endrpt) { + + if (codeGroupAr[i].equals("51515")) { + endrpt = true; + break; + } + + String ppIndicator = codeGroupAr[i].substring(0, 2); + // Check that first two characters are 44 or 55. + if (ppIndicator.equals("44") || ppIndicator.equals("55")) { + String skip = codeGroupAr[i].substring(2, 5); + if (skip.equals("///")) { + i++; + } else { + // Decode number of pressures which is the third character. + /* chin, fix bug, when a non-number char is decoded */ + int onetothree = 0; + try { + onetothree = Integer.parseInt(codeGroupAr[i].substring( + 2, 3)); + } catch (NumberFormatException e) { + onetothree = 0; + + } + if (onetothree > 0 && onetothree <= 3) { + // Decode the pressure + float[] presArr = { + IDecoderConstantsN.UAIR_FLOAT_MISSING, + IDecoderConstantsN.UAIR_FLOAT_MISSING, + IDecoderConstantsN.UAIR_FLOAT_MISSING, }; + presArr = getPressureFromPPAACC(codeGroupAr[i], above); + + i++; + for (int index = 0; index < onetothree; index++) { + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float ppp = presArr[index]; + if (ppp < pold) { + pres = presArr[index]; + pold = ppp; + } + + // Decode wind group + if (i < cgSize) { + NcUairWindGroup.WindField(codeGroupAr[i], + windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + } + + // Add level + addLevels(record, pres, height, temp, dewpTemp, + wdir, wspeed); + + i++; + } + } else { + // no pressure group + i++; + } + } + } else if (ppIndicator.equals("77") || ppIndicator.equals("66")) { + // Decode maximum wind group + String hhh = codeGroupAr[i].substring(2, 5); + if (hhh.equals("999")) { + // NO maximum wind and wind shear data + i++; + } else { + processMaximumWindShear(codeGroupAr, i, cgSize, windKnot, + above, record); + i = i + 3; + } + } else { + i++; + break; + } + } + } + + /** + * Decodes a pressure/height and temperature fields for PPBBDD. These + * reports contain significant wind data above 100 mb. + * + * @param codeGroupAr + * The input uppair code group array + * @param cgSize + * The input cgSize is the size of the code group array + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processPPBBDD(String[] codeGroupAr, int cgSize, + Boolean windKnot, NcUairRecord record) { + + Boolean endrpt = false; + Boolean pressflag = false; + Boolean above = false; + + String dataType = record.getDataType(); + + if (dataType.substring(2, 4).equals("DD")) { + above = true; + } + + int i = 0; + try { + while (i < cgSize && !endrpt) { + + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + if (codeGroupAr[i].equals("21212")) { + pressflag = true; + i++; + if (i >= cgSize) { + break; + } + // System.out.println("got 21212 in PP BBDD - need to process...\n"); + break; + } + + if (codeGroupAr[i].equals("51515") + || codeGroupAr[i].equals("41414")) { + endrpt = true; + // System.out.println("got end group 51515 41414 in PP BBDD - BREAK \n"); + break; + } + + // Convert the unit digits to integers and compute height. + Boolean missing = false; + int noWind = 0; + if (!pressflag) { + while (!missing && (noWind < 3)) { + int index = noWind + 3; + String unit = "/"; + unit = codeGroupAr[i].substring(index - 1, index); + + if (unit.equals(("/"))) { + missing = true; + break; + } + + // Decode height; invalid height if returns + // FLOAT_MISSING. + height = getHeightFromPPBBDD(codeGroupAr[i], index); + if (height == IDecoderConstantsN.UAIR_FLOAT_MISSING) { + i++; + break; + } + int range = i + noWind + 1; + if (range < cgSize) { + // Decode wind group. + NcUairWindGroup.WindField(codeGroupAr[i + noWind + + 1], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + } + + // Add level + addLevels(record, pres, height, temp, dewpTemp, wdir, + wspeed); + + noWind++; + } + + i = i + noWind + 1; + } else { + int range = i + 1; + if (range < cgSize) { + // Decode pressure group + pres = getPressureFromTTBBDD(codeGroupAr[i], above); + NcUairWindGroup.WindField(codeGroupAr[i + 1], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + } + i = i + 2; + } + } + } catch (Exception e) { + + } + } + + /** + * Decodes a pressure field for mandatory wind reports (PPAA or PPCC). The + * pressures reporting are returned. + * + * @param presGroup + * The input pressure code group + * @param above + * The input above is flag + * @return pressure + */ + public static float[] getPressureFromPPAACC(String presGroup, Boolean above) { + + float pressure[] = { IDecoderConstantsN.UAIR_FLOAT_MISSING, + IDecoderConstantsN.UAIR_FLOAT_MISSING, + IDecoderConstantsN.UAIR_FLOAT_MISSING }; + + String bpres[] = { "00", "92", "85", "70", "50", "40", "30", "25", + "20", "15", "10" }; + + String apres[] = { "70", "50", "30", "20", "10", "07", "05", "03", + "02", "01" }; + + int rbprs[] = { 1000, 925, 850, 700, 500, 400, 300, 250, 200, 150, 100 }; + + int raprs[] = { 70, 50, 30, 20, 10, 7, 5, 3, 2, 1 }; + + int level = -1; + + String hh = presGroup.substring(3, 5); + // Check first pressure against list. + int ifirst = -1; + if (!above) { + for (String plevel : bpres) { + ifirst++; + if (hh.equals(plevel)) { + level = ifirst; + break; + } + } + } else { + for (String plevel : apres) { + ifirst++; + if (hh.equals(plevel)) { + level = ifirst; + break; + } + } + } + + if (!above) { + if (ifirst < 11 && level >= 0) { + pressure[0] = rbprs[level]; + if (level + 1 < 11) { + pressure[1] = rbprs[level + 1]; + } + if (level + 2 < 11) { + pressure[2] = rbprs[level + 2]; + } + } + } else { + if (ifirst < 10 && level >= 0) { + pressure[0] = raprs[level]; + if (level + 1 < 10) { + pressure[1] = raprs[level + 1]; + } + if (level + 2 < 10) { + pressure[2] = raprs[level + 2]; + } + } + } + + return pressure; + } + + /** + * Decodes the height from a significant wind field from reports (PPBB or + * PPDD). The field is of the form atuuu where a is 1 if the height is above + * 100000 feet and 9 or 8 otherwise. t is the ten-thousands digit of the + * height and the u's are up to three thousands-of-feet fields. The heights + * are converted from feet to meters. + * + * @param heightGroup + * The input heightGroup contains the height code group. + * @param index + * The input index is the current position in the code group. + * @return pressure + */ + public static float getHeightFromPPBBDD(String heightGroup, int index) { + float height = IDecoderConstantsN.UAIR_FLOAT_MISSING; + final double feetToMeter = 0.3048; + + String hhIndicator = heightGroup.substring(0, 1); + int iadd = 0; + // Check that the first character of the field is '9' or '1'. + if (hhIndicator.equals("9") || hhIndicator.equals("8")) { + iadd = 0; + } else { + return height; + } + + // Convert the tens digit to an integer. + int iten; + iten = Integer.parseInt(heightGroup.substring(1, 2)) * 10; + int iunit = Integer.parseInt(heightGroup.substring(index - 1, index)); + height = (iten + iunit) * 1000 + iadd; + height = (float) (height * feetToMeter); + return height; + } + + /** + * Decodes a pressure field from a group reports (TTBB or TTDD). The + * pressures reporting are returned. + * + * @param presGroup + * The input pressure code group + * @param above + * The input above is flag + * @return pressure + */ + public static float getPressureFromTTBBDD(String presGroup, Boolean above) { + + float pressure = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + String clev[] = { "00", "11", "22", "33", "44", "55", "66", "77", "88", + "99" }; + + if (presGroup.length() == 5) { + String pp = presGroup.substring(0, 2); + String hhh = presGroup.substring(2, 5); + + int level = -1; + if (!pp.equals("//") && !hhh.substring(2, 3).equals("/")) { + for (String plevel : clev) { + level++; + if (pp.equals(plevel)) { + break; + } + } + // If a level was found, decode the pressure. + if (level != -1 && level < 10) { + int ihhh = Integer.parseInt(hhh); + if (above) { + pressure = (float) ihhh / (float) 10; + } else { + if (ihhh < 100) { + pressure = ihhh + 1000; + } else { + pressure = ihhh; + } + } + } + } + } + return pressure; + } + + /** + * Decodes data for the tropopause level from the TTAA reports. The output + * data are ordered PRES TEMP DWPT DRCT SPED. + * + * @param codeGroupAr + * The input uppair code group array + * @param index + * The input index is the current position of the code group + * array + * @param topwindFlag + * the input index is the flag to indicate top wind + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processTropopause(String[] codeGroupAr, int index, + Boolean topwindFlag, Boolean windKnot, NcUairRecord record, + Boolean above) { + + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float temp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float dewpTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + int i = index; + + String pp = codeGroupAr[i].substring(0, 2); + String hhh = codeGroupAr[i].substring(2, 5); + + if (pp.equals("88")) { + // Decode pressure and height + pres = Integer.parseInt(hhh); + if (above) { + pres = pres / 10.f; + } + + // Decode temperature group + NcUairTempGroup.TempField(codeGroupAr[i + 1]); + temp = NcUairTempGroup.getTemperature(); + dewpTemp = NcUairTempGroup.getDewpointTemp(); + + if (!topwindFlag) { + // Decode wind group + NcUairWindGroup.WindField(codeGroupAr[i + 2], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + } + + addTrop(record, pres, temp, dewpTemp, wdir, wspeed); + } + } + + /** + * Decodes data for the maximum wind level(s) and wind shear from the TTAA + * reports. The output data are ordered PRES DRCT SPED. + * + * @param codeGroupAr + * The input uppair code group array + * @param index + * The input index is the current position of the code group + * array + * @param cgSize + * the input cgSize is the size of the codeGroupAr. + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processMaximumWindShear(String[] codeGroupAr, int index, + int cgSize, Boolean windKnot, Boolean above, NcUairRecord record) { + + float pres = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdir = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeed = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float loShear = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float hiShear = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + int i = index; + + String ppIndicator = codeGroupAr[i].substring(0, 2); + String hhh = codeGroupAr[i].substring(2, 5); + + if (ppIndicator.equals("77") || ppIndicator.equals("66")) { + // Decode pressure and height + if (!hhh.substring(1, 3).equals("//")) { + if (above) { + pres = Integer.parseInt(hhh) / 10.f; + } else { + pres = Integer.parseInt(hhh); + } + } + + // Decode wind group + NcUairWindGroup.WindField(codeGroupAr[i + 1], windKnot); + wspeed = NcUairWindGroup.getSped(); + wdir = NcUairWindGroup.getDrct(); + + if (i + 2 < cgSize) { + String windshearIndicator = codeGroupAr[i + 2].substring(0, 1); + if (windshearIndicator.equals("4")) { + String below = codeGroupAr[i + 2].substring(1, 3); + String abov = codeGroupAr[i + 2].substring(3, 5); + if (!below.equals("//")) { + loShear = Integer.parseInt(below); + } + if (!abov.equals("//")) { + hiShear = Integer.parseInt(abov); + } + } + } + + NcUairMaxWind mwind = null; + mwind = new NcUairMaxWind(); + mwind.setPres(pres); + mwind.setDrct(wdir); + mwind.setSped(wspeed); + mwind.setLoShear(loShear); + mwind.setHiShear(hiShear); + + record.addMaxWind(mwind); + } + } + + /** + * Decodes data for the lifted index group from the TTAA reports. The output + * data are ordered liTemp, wdirSurface, wspeedSurface, wdirAbove, + * wspeedAbove + * + * @param codeGroupAr + * The input uppair code group array + * @param index + * The input index is the current position of the code group + * array + * @param windKnot + * The input windKnot is flag to indicate wind in knot or not. + * @param record + * The in and out Uair record + * @return + */ + public static void processLiftedIndex(String[] codeGroupAr, int index, + Boolean windKnot, NcUairRecord record) { + + float liTemp = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdirSurface = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeedSurface = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wdirAbove = IDecoderConstantsN.UAIR_FLOAT_MISSING; + float wspeedAbove = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + int i = index; + if (codeGroupAr[i].equals("10164")) { + int ittt = Integer.parseInt(codeGroupAr[i + 1]); + if (ittt > 50) { + // subtract 50 if lllll greater than 50 + ittt = ittt - 50; + } + int isign = ittt % 2; + /* + * If the integer is even, the liTemp is positive. Otherwise, the + * liTemp is negative. + */ + if (isign == 1) { + liTemp = -ittt; + } else { + liTemp = ittt; + } + liTemp = (float) (liTemp / 10.); + i = i + 2; + } + + if (codeGroupAr[i].equals("10194")) { + // Decode wind group from surface to 5000ft + NcUairWindGroup.WindField(codeGroupAr[i + 1], windKnot); + wspeedSurface = NcUairWindGroup.getSped(); + wdirSurface = NcUairWindGroup.getDrct(); + + // Decode wind group from 5000ft to 10000ft + NcUairWindGroup.WindField(codeGroupAr[i + 2], windKnot); + wspeedAbove = NcUairWindGroup.getSped(); + wdirAbove = NcUairWindGroup.getDrct(); + + NcUairLiftedIndex li = null; + li = new NcUairLiftedIndex(); + li.setLiTemp(liTemp); + li.setLoDrct(wdirSurface); + li.setLoSped(wspeedSurface); + li.setHiDrct(wdirAbove); + li.setHiSped(wspeedAbove); + record.addLiftedIndex(li); + } + } + + /** + * Compute the top pressure reporting wind data. If topwind is missing, set + * return top wind to a large value so that all the winds will be assumed to + * be missing. + * + * @param topwind + * The input topwind + * @param above + * The input above is flag to indicate above 100mb. + * @return an int for topWind. + */ + public static int getTopWind(int topwind, Boolean above) { + + int retTopWind = 2000; + + if (topwind == IDecoderConstantsN.UAIR_INTEGER_MISSING) { + retTopWind = 2000; + } else if (topwind > 0) { + if (above) { + retTopWind = topwind * 10; + } else { + retTopWind = topwind * 100; + } + } else if (topwind == 0) { + if (!above) { + retTopWind = 1000; + } else { + retTopWind = 0; + } + } else { + retTopWind = 2000; + } + return retTopWind; + } + + /** + * Add levels to record. + * + * @param record + * The in and out Uair record + * @param pres + * The input pressure + * @param hght + * The input geopotential height + * @param temp + * The input temperature + * @param dwpt + * The input dew point temperature. + * @param drct + * The input wind direction + * @param sped + * The input wind speed + * @return + */ + public static void addLevels(NcUairRecord record, float pres, + float geoHeight, float temp, float dwpt, float drct, float sped) { + + NcUairObsLevels level = null; + level = new NcUairObsLevels(); + level.setPres(pres); + level.setHght(geoHeight); + level.setTemp(temp); + level.setDwpt(dwpt); + level.setDrct(drct); + level.setSped(sped); + + record.addObsLevels(level); + } + + /** + * Add tropopause to record. + * + * @param record + * The in and out Uair record + * @param pres + * The input pressure + * @param temp + * The input temperature + * @param dwpt + * The input dew point temperature. + * @param drct + * The input wind direction + * @param sped + * The input wind speed + * @return + */ + public static void addTrop(NcUairRecord record, float pres, float temp, + float dwpt, float drct, float sped) { + + NcUairTropopause trop = null; + trop = new NcUairTropopause(); + trop.setPres(pres); + trop.setTemp(temp); + trop.setDwpt(dwpt); + trop.setDrct(drct); + trop.setSped(sped); + + record.addTropopause(trop); + } - // Decode wind group from 5000ft to 10000ft - NcUairWindGroup.WindField(codeGroupAr[i+2], windKnot); - wspeedAbove = NcUairWindGroup.getSped(); - wdirAbove = NcUairWindGroup.getDrct(); - - NcUairLiftedIndex li = null; - li = new NcUairLiftedIndex(); - li.setLiTemp(liTemp); - li.setLoDrct(wdirSurface); - li.setLoSped(wspeedSurface); - li.setHiDrct(wdirAbove); - li.setHiSped(wspeedAbove); - record.addLiftedIndex(li); - } - } - - /** - * Compute the top pressure reporting wind data. - * If topwind is missing, set return top wind to a large value so that - * all the winds will be assumed to be missing. - * - * @param topwind The input topwind - * @param above The input above is flag to indicate above 100mb. - * @return an int for topWind. - */ - public static int getTopWind(int topwind, Boolean above) { - - int retTopWind = 2000; - - if ( topwind == IDecoderConstantsN.UAIR_INTEGER_MISSING ) { - retTopWind = 2000; - } else if ( topwind > 0 ) { - if ( above ) { - retTopWind = topwind * 10; - } else { - retTopWind = topwind * 100; - } - } else if ( topwind == 0 ) { - if ( ! above ) { - retTopWind = 1000; - } else { - retTopWind = 0; - } - } else { - retTopWind = 2000; - } - return retTopWind; - } - - /** - * Add levels to record. - * - * @param record The in and out Uair record - * @param pres The input pressure - * @param hght The input geopotential height - * @param temp The input temperature - * @param dwpt The input dew point temperature. - * @param drct The input wind direction - * @param sped The input wind speed - * @return - */ - public static void addLevels(NcUairRecord record, float pres, float geoHeight, - float temp, float dwpt, float drct, float sped) { - - NcUairObsLevels level = null; - level = new NcUairObsLevels(); - level.setPres(pres); - level.setHght(geoHeight); - level.setTemp(temp); - level.setDwpt(dwpt); - level.setDrct(drct); - level.setSped(sped); - - record.addObsLevels(level); - } - - /** - * Add tropopause to record. - * - * @param record The in and out Uair record - * @param pres The input pressure - * @param temp The input temperature - * @param dwpt The input dew point temperature. - * @param drct The input wind direction - * @param sped The input wind speed - * @return - */ - public static void addTrop(NcUairRecord record, float pres, - float temp, float dwpt, float drct, float sped) { - - NcUairTropopause trop = null; - trop = new NcUairTropopause(); - trop.setPres(pres); - trop.setTemp(temp); - trop.setDwpt(dwpt); - trop.setDrct(drct); - trop.setSped(sped); - - record.addTropopause(trop); - } - } diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.ntrans/res/spring/ntrans-ingest.xml b/ncep/gov.noaa.nws.ncep.edex.plugin.ntrans/res/spring/ntrans-ingest.xml index 3be95f7491..b5f934cf88 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.ntrans/res/spring/ntrans-ingest.xml +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.ntrans/res/spring/ntrans-ingest.xml @@ -13,7 +13,7 @@ - + + ^NTRANS.* diff --git a/ncep/gov.noaa.nws.ncep.edex.plugin.solarimage/utility/edex_static/base/distribution/solarimage.xml b/ncep/gov.noaa.nws.ncep.edex.plugin.solarimage/utility/edex_static/base/distribution/solarimage.xml index ed85754402..cdba2b0bcb 100644 --- a/ncep/gov.noaa.nws.ncep.edex.plugin.solarimage/utility/edex_static/base/distribution/solarimage.xml +++ b/ncep/gov.noaa.nws.ncep.edex.plugin.solarimage/utility/edex_static/base/distribution/solarimage.xml @@ -1,4 +1,4 @@ - ^SXI_* + ^SIMPLE* diff --git a/ncep/gov.noaa.nws.ncep.edex.uengine/gemglb.nts b/ncep/gov.noaa.nws.ncep.edex.uengine/gemglb.nts deleted file mode 100644 index 6ae69f473a..0000000000 --- a/ncep/gov.noaa.nws.ncep.edex.uengine/gemglb.nts +++ /dev/null @@ -1,191 +0,0 @@ -ADDSTN YES -AFOSFL -AIRM -ANOTLN -ANOTYP -AWPSFL -ANLYSS 4/2;2;2;2 -AREA @dca;iad;bwi -ATCF -BORDER 1 -BOXLIN -BUFRFIL -CENTER -CINT 0 -CLEAR YES -CLRBAR -CNTRFL -COLORS 1 -COLUMN 1 -CONTUR 0 -CPYFIL -CSIG -CTLFLG YES -CTYPE C -CURVE 2 -CXSTNS -DATTIM 18-20 -DELTAN -DELTAX -DELTAY -DEVICE XW -DTAAREA -EDGEOPTS -ENCY -EXTEND 2;2;2;2 -FHOUR -FILTER YES -FINT 0 -FLINE 10-20 -FXYTBL -GAMMA 0.3 -GAREA WV -IJSKIP -GBDIAG -GBFILE -GBTBLS -GDATTIM LAST -GDEFIL GDLIST.FIL -GDFILE $GEMDATA/HRCBOB.GRD -GDNUM LIST -GDOUTF -GDPFUN TMPC -GFUNC TMPC -GGLIMS -GGVGF -GLEVEL 500 -GPACK -GPOINT IAD -GRDAREA -GRDHDR -GRDLBL 0 -GRDNAM -GRDTYP S -GSKIP -GUESS -GUESFUN -GVCORD PRES -GVECT WND -G2DIAG -G2DRT -G2IDS -G2IS -G2PDT -G2TBLS -HILO -HISTGRD NO -HLSYM -HRCN -IDNTYP STID -IMCBAR -INDXFL -INFO -ISIG -KXKY 10;10 -LAT -LATLON -LEVELS 500 -LINE 3 -LOCI -LON -LSTALL YES -LSTPRM -LTNG -LUTFIL -MAP 1 -$MAPFIL HIPOWO.CIA -MARKER 0 -MAXGRD 200 -MIXRLN 0 -MSCALE 0 -MRGDAT YES -NCON -NPASS 2 -NTRACE 5 -OUTFIL -OUTPUT t -OVERWR NO -PANEL 0 -PDSEXT NO -PIXRES 1 -PLUS -POSN 0 -PRBTYP -PROJ MER -PTYPE LOG -QCNTL -QSCT -RADFIL -REGION VIEW -REFVEC -$RESPONDYES -SATFIL -FAXFIL -SAVFIL -SCALE 999 -SEARCH 20 -SFEFIL SFLIST.FIL -SFFILE /export-2/cdbbkp/ldmcdb/data/decoders/hrly/20110530.hrly -SFFSRC -SFOUTF -SFPARM dset -SFPRMF METAR.PACK -SHAPE -SHIPFL NO -SHRTTL -SKIP 0 -SKPMIS y -SNBUFR -SNEFIL SNLIST.FIL -SNFILE $GEMDATA/HRCBOB.SND -SNOUTF -SNPARM ;TMPC;;HGHT;DWPC;BRBM -SNPRMF SNMERG.PACK -SOURCE SF -STATION BWI -STNCOL 1 -STNDEX SHOW -STNFIL SFSTNS.TBL -STNPLT -STNTYP A -STREAM -STRMID -SVRL -TAXIS -TCMG -TEXT 1 -THTALN 0 -THTELN 0 -TIMSTN 1/0 -TITLE 1 -TRACE1 TMPF;DWPF:3/2;3 -TRACE2 PMSL/4 -TRACE3 SKNT;GUST;DARR/1 -TRACE4 VSBY/7 -TRACE5 CLDS;;WSYM/6 -TRAK1 -TRAK2 -TRAKE -TXTCOL -TXTFIL -TXTLOC -TXTYPE -TYPE C -UKAFIL -VCOORD PRES -VGFILE -WARN -WATCH -WIND BM1 -WINPOS 1 -WSTM -XAXIS -YAXIS -WOU -WCN -WCP -WSAT -FHR -TAG -STAT -ASCT diff --git a/ncep/gov.noaa.nws.ncep.edex.uengine/last.nts b/ncep/gov.noaa.nws.ncep.edex.uengine/last.nts deleted file mode 100644 index 6b9be5e181..0000000000 --- a/ncep/gov.noaa.nws.ncep.edex.uengine/last.nts +++ /dev/null @@ -1,7 +0,0 @@ -SFFILE /export-2/cdbbkp/ldmcdb/data/decoders/hrly/20110530.hrly -AREA @dca;iad;bwi -DATTIM 18-20 -OUTPUT t -SKPMIS y -IDNTYP STID -SFPARM dset diff --git a/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MergeSounding.java b/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MergeSounding.java index a73f05cb1a..499a2c7789 100644 --- a/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MergeSounding.java +++ b/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MergeSounding.java @@ -1,14 +1,14 @@ package gov.noaa.nws.ncep.edex.uengine.tasks.profile; +import gov.noaa.nws.ncep.common.tools.IDecoderConstantsN; +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; //import edu.emory.mathcs.backport.java.util.Collections; -import gov.noaa.nws.ncep.common.tools.IDecoderConstantsN; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer2; /** * @@ -29,9 +29,9 @@ import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer2; * 12/2010 301 T. Lee/NCEP Re-factored for BUFRUA * 5/10/2011 301 C. Chen added rhToDewpoint(), tempToVapr() * 02/28/2012 Chin Chen modify several sounding query algorithms for better performance - * 8/2012 T. Lee/NCEP Removed missing wind interpolation - * 8/2012 T. Lee/NCEP Fixed max wind merging; May fix NSHARP EL calculation - + * 8/2012 T. Lee/NCEP Removed missing wind interpolation + * 8/2012 T. Lee/NCEP Fixed max wind merging; May fix NSHARP EL calculation + * 12/2013 T. Lee/NCEP Fixed missing height at top level before sorting * * * @author T. Lee @@ -39,1664 +39,1700 @@ import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer2; */ public class MergeSounding { - final float RMISSD = IDecoderConstantsN.UAIR_FLOAT_MISSING; - final int IMISSD = IDecoderConstantsN.INTEGER_MISSING; - - /** - * Default constructor - */ - public MergeSounding() { - } - - /* - * Process native sounding data. Convert specific humidity to dew point - * temperature then compute the moist height. - */ - public List nativeModelSounding(List sls, - float elevation) { - spfhToDewpoint(sls); - constructHeight(sls, elevation); - return sls; - } - - /* - * Process upper air sounding data. Note that TTAA is the original/sorted - * data, while MAN is the TTAA without underground data and MAN_D is the - * TTAA for display, i.e., the first level is the surface level, any under - * -ground levels will be above the surface level. - */ - public List mergeUairSounding(String level, - List ttaa, Listttbb, - List ttcc, Listttdd, - List ppaa, Listppbb, - List ppcc, Listppdd, - List trop_a, List trop_c, - List wmax_a, List wmax_c, - float elevation ) { - List sndata = new ArrayList(); - List man = null; -//System.out.println ( "From mergeUairSounding " ); - /*///for debug -//System.out.println("TTAA:"); - for (NcSoundingLayer l: ttaa){ - //System.out.println("P="+l.getPressure()+"H="+l.getGeoHeight()+"T="+l.getTemperature()+"D="+l.getDewpoint()+"Wd="+l.getWindDirection()+"Ws="+l.getWindSpeed()); - } -//System.out.println("TTBB:"); - for (NcSoundingLayer l: ttbb){ - //System.out.println("P="+l.getPressure()+"H="+l.getGeoHeight()+"T="+l.getTemperature()+"D="+l.getDewpoint()+"Wd="+l.getWindDirection()+"Ws="+l.getWindSpeed()); - } -//System.out.println("PPAA:"); - for (NcSoundingLayer l: ppaa){ - //System.out.println("P="+l.getPressure()+"H="+l.getGeoHeight()+"T="+l.getTemperature()+"D="+l.getDewpoint()+"Wd="+l.getWindDirection()+"Ws="+l.getWindSpeed()); - } -//System.out.println("PPBB:"); - for (NcSoundingLayer l: ppbb){ - //System.out.println("P="+l.getPressure()+"H="+l.getGeoHeight()+"T="+l.getTemperature()+"D="+l.getDewpoint()+"Wd="+l.getWindDirection()+"Ws="+l.getWindSpeed()); - }*/ - // Return the specific levels requested by users - if ( ttaa.size() > 0 ) { - Collections.sort(ttaa, new reverseSortByPressure()); - //System.out.println("TTAA sounding: "); - //for ( NcSoundingLayer soundLy : ttaa ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - if (level.toUpperCase().equalsIgnoreCase("MAN")) { - return ttaa; - } - man = removeUnderGround(ttaa); - - } else { - man=ttaa; - if (level.toUpperCase().equalsIgnoreCase("MAN")) { - return setMissing(); - } - - } - - - // Sorting the data - - if ( ttbb.size() > 0) { - Collections.sort(ttbb, new reverseSortByPressure()); - //System.out.println("TTBB sounding: "); - //for ( NcSoundingLayer soundLy : ttbb ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - - if ( ttcc.size() > 0) { - Collections.sort(ttcc, new reverseSortByPressure()); - //System.out.println("TTCC sounding: "); - ////for ( NcSoundingLayer soundLy : ttcc ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - - if ( ttdd.size() > 0) { - Collections.sort(ttdd, new reverseSortByPressure()); - //System.out.println("TTDD sounding: "); - //for ( NcSoundingLayer soundLy : ttdd ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - - if ( ppaa.size() > 0 ) { - if (checkWindData(ppaa)) { - Collections.sort(ppaa, new MergeSounding.sortByHeight()); - //System.out.println("TTAA sounding: "); - //for ( NcSoundingLayer soundLy : ttaa ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } else { - Collections.sort(ppaa, new MergeSounding.reverseSortByPressure()); - //System.out.println("TTAA sounding: "); - //for ( NcSoundingLayer soundLy : ttaa ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - } - - - if ( ppcc.size() > 0 ) { - if (checkWindData(ppcc)) { - Collections.sort(ppcc, new MergeSounding.sortByHeight()); - //System.out.println("PPCC sounding: "); - //for ( NcSoundingLayer soundLy : ppcc ){ - // System.out.print(soundLy.getPressure() + " , "); - // } - //System.out.println(); - } else { - Collections.sort(ppcc, new MergeSounding.reverseSortByPressure()); - //System.out.println("PPCC sounding: "); - //for ( NcSoundingLayer soundLy : ppcc ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - } - - if ( ppbb.size() > 0 ) { - if (checkWindData(ppbb)) { - Collections.sort(ppbb, new MergeSounding.sortByHeight()); - //System.out.println("PPBB sounding: "); - //for ( NcSoundingLayer soundLy : ppbb ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } else { - Collections.sort(ppbb, new MergeSounding.reverseSortByPressure()); - //System.out.println("PPBB sounding: "); - //for ( NcSoundingLayer soundLy : ppbb ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - } - - if ( ppdd.size() > 0 ) { - - if (checkWindData(ppdd)) { - Collections.sort(ppdd, new MergeSounding.sortByHeight()); - //System.out.println("PPDD sounding: "); - //for ( NcSoundingLayer soundLy : ppdd ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } else { - Collections.sort(ppdd, new MergeSounding.reverseSortByPressure()); - //System.out.println("PPDD sounding: "); - //for ( NcSoundingLayer soundLy : ppdd ){ - // System.out.print(soundLy.getPressure() + " , "); - //} - //System.out.println(); - } - - } - - // Find surface data, return if users request surface data only. - NcSoundingLayer sl = new NcSoundingLayer(); - sl = getSurfaceData(man, ttbb, ppbb, elevation); - sndata.add(0, sl); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling getSurfaceData(): " + sndata.size() ); - if ( isNumber(level) >= 0 ) { - if ( equal(0.f, Float.valueOf(level.trim()).floatValue()) || - equal(sl.getPressure(), Float.valueOf(level.trim()).floatValue())) { - //System.out.println("Returning surface data...."); - return sndata; - } - } - - // Merge mandatory data -//System.out.println ( "Just before calling mergeMandatory() " ); - mergeMandatory(man, ttcc, sndata); - int sndataSize = sndata.size(); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling mergeMandatory(): " + sndata.size() ); - - // Check if the single level is mandatory or not - if ( isNumber(level) >= 0 ) { - for ( int kk = 0; kk < sndata.size(); kk++) { - if ( equal(Float.valueOf(level.trim()).floatValue(), sndata.get(kk).getPressure())) { - sl.setPressure(sndata.get(kk).getPressure()); - sl.setTemperature(sndata.get(kk).getTemperature()); - sl.setDewpoint(sndata.get(kk).getDewpoint()); - sl.setWindDirection(sndata.get(kk).getWindDirection()); - sl.setWindSpeed(sndata.get(kk).getWindSpeed()); - sl.setGeoHeight(sndata.get(kk).getGeoHeight()); - sndata.clear(); - sndata.add(sl); - //System.out.println( "single layer of sounding added to sndata" ); - return sndata; - } - } - } - - // Merge mandatory winds - mergeMandatoryWinds (ppaa, ppcc, sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling mergeMandatoryWinds(): " + sndata.size() ); - // Merge tropopause - mergeTropSigTemp (trop_a, trop_c, sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " mergeTropSigTemp() for merging trop_a and trop_c: " + sndata.size() ); - // Merge TTBB - mergeTropSigTemp (ttbb, ttdd, sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " mergeTropSigTemp() for merging ttbb and ttdd: " + sndata.size() ); - // Construct height for ttbb - constructTtbbHeight(sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " constructTtbbHeight(): " + sndata.size() ); - // Merge significant winds on pressure surfaces - if (!checkWindData(ppbb)) { - mergeSigMaxWindOnPressure(ppbb,ppdd,sndata); - //System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " mergeSigMaxWindOnPressure() for merging ppbb and ppdd: " + sndata.size() ); - } - - mergeSigMaxWindOnPressure(wmax_a,wmax_c,sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " mergeSigMaxWindOnPressure() for merging wmax_a and wmax_c: " + sndata.size() ); - - constructPpbbHeight(sndata); -//System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " constructPpbbHeight()" + sndata.size() ); - if (checkWindData(ppbb)) { - mergeSigWindOnHeight(ppbb,ppdd,sndata); - //System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " mergeSigWindOnHeight() for merging ppbb and ppdd: " + sndata.size() ); - constructPpbbPressure(sndata); - //System.out.println ( "sndataSize in mergeUairSounding(), after calling" + -// " constructPpbbPressure()" + sndata.size() ); - } - - // Reorder sounding profile so the first level is the surface data. - // No need to reorder now becuz surface data is always the 1st level. - //reOrderSounding(sndata); - - // Interpolate missing temperature, dew point and winds. - constructMissing(1,sndata); - constructMissing(2,sndata); - //constructMissing(3,sndata); - - // Return single level or add underground mandatory data to the sounding profile - if ( isNumber (level) == 0 ) { - float rlev = new Integer(Integer.parseInt(level.trim())).floatValue(); - return getSingLevel(rlev, sndata); - } else if ( isNumber (level) == 1 ) { - float rlev = new Float(Float.parseFloat(level.trim())); - return getSingLevel(rlev, sndata); - } else { - return addUnderGround(ttaa,sndata); - } - - } - - /* - * Check an alpha-numerical string is a number or characters. - */ - public int isNumber (String level) { - try { - if (Integer.parseInt(level) >=0 ) { - return 0; - } else { - return -1; - } - } catch (NumberFormatException nfe1){ - try { - if (Float.parseFloat(level) >= 0.f) { - return 1; - } else { - return -1; - } - } catch (NumberFormatException nfe2) { - try { - if (Double.parseDouble(level) >= 0.) { - return 2; - } else { - return -1; - } - } catch (NumberFormatException nfe3) { - return -1; - } - } - } - } - - /* - * convert specific humidity to dew point temperature. - */ - float elevation; - - public List spfhToDewpoint(List sndata) { - float spfh, pres; - float dwpc = RMISSD; - - for (NcSoundingLayer layer: sndata) { - if (layer.getDewpoint() == RMISSD) { - spfh = layer.getSpecHumidity(); - pres = layer.getPressure(); - - if (spfh == RMISSD || pres == RMISSD || spfh <= 0.f - || pres <= 0.f) { - continue; - } else { - float rmix = spfh / (1.f - spfh); - float e = (pres * rmix) / (.62197f + rmix); - e = e / (1.001f + ((pres - 100.f) / 900.f) * .0034f); - dwpc = (float) (Math.log(e / 6.112) * 243.5 / (17.67 - Math - .log((e / 6.112)))); - layer.setDewpoint(dwpc); - ////System.out.println("spfhToDewpoint dwpc: " + dwpc); - } - - } - } - return sndata; - } - /* - * computes DWPC from TMPC and RELH - * Note: If DWPC is less than -190 degrees C, it is treated as - * missing data - * Code is based on GEMPAK's prrhdp.f - */ - public List rhToDewpoint(List sndata) { - float rh, vapr,vaps, temp; - float dwpc = RMISSD; - - for (NcSoundingLayer layer: sndata) { - if (layer.getDewpoint() == RMISSD) { - rh = layer.getRelativeHumidity(); - temp = layer.getTemperature(); - - if (rh == RMISSD || temp == RMISSD ) { - continue; - } else { - vaps = tempToVapr(temp); - vapr = rh * vaps /100; - if(vapr < Math.exp(-30)) - continue; - else { - dwpc = (float) (243.5 * ( Math.log(6.112) - Math.log(vapr)) / (Math.log(vapr) - Math.log(6.112)-17.67)); - layer.setDewpoint(dwpc); - ////System.out.println("rhToDewpoint dwpc: " + dwpc); - } - } - } - } - return sndata; - } - /* - * computes VAPR from TMPC - * Code is based on GEMPAK's prvapr.f - */ - private float tempToVapr(float temp){ - return(float)(6.112 * Math.exp((17.67* temp)/(temp+243.5))); - } - - private void constructHeight(List sndata, float elev) { - - /* - * For native model sounding, using hypsometric equation to build height - */ - elevation = elev; - int lev = sndata.size(); - float tb = RMISSD, tdb = RMISSD, pb = RMISSD; - float tt = RMISSD, tdt = RMISSD, pt = RMISSD; - float dwptsf, psfc, tmpcsf, scaleh, mhgt = RMISSD; - - for (int k = 0; k < lev; k++) { - - if (k == 0) { - tmpcsf = sndata.get(k).getTemperature(); - dwptsf = sndata.get(k).getDewpoint(); - psfc = sndata.get(k).getPressure(); - tb = tmpcsf; - tt = tmpcsf; - tdb = dwptsf; - tdt = dwptsf; - pb = psfc; - pt = psfc; - - scaleh = scaleHeight(tb, tt, tdb, tdt, pb, pt); - mhgt = moistHeight(elevation, pb, pt, scaleh); - } else { - tt = sndata.get(k).getTemperature(); - tdt = sndata.get(k).getDewpoint(); - pt = sndata.get(k).getPressure(); - scaleh = scaleHeight(tb, tt, tdb, tdt, pb, pt); - - mhgt = moistHeight(mhgt, pb, pt, scaleh); - tb = tt; - tdb = tdt; - pb = pt; - - } - sndata.get(k).setGeoHeight(mhgt); - } - } - - /* - * Compute moist height. - */ - private float moistHeight(float zb, float pb, float pt, float scale) { -//System.out.println("From moistHeight: "); - if (zb == RMISSD || pb == RMISSD || pt == RMISSD || scale == RMISSD) { - return RMISSD; - } else { - //System.out.println("the computed moistHeight is " + (float) (zb + scale * Math.log(pb / pt))); - return (float) (zb + scale * Math.log(pb / pt)); - } - } - - /* - * Compute scale height. - */ - private float scaleHeight(float tb, float tt, float tdb, float tdt, - float pb, float pt) { -//System.out.println("From scaleHeight: " ); - final float RDGAS = 287.04f, GRAVTY = 9.80616f, RKAP = RDGAS / GRAVTY; - if (tb == RMISSD || tt == RMISSD || pb == RMISSD || pt == RMISSD) { - return RMISSD; - } else { - float tvb = virtualTemperature(tb, tdb, pb); - float tvt = virtualTemperature(tt, tdt, pt); - //System.out.println("tvb = " + tvb); - //System.out.println("tvt = " + tvt); - if (tvb == RMISSD || tvt == RMISSD) { - return RMISSD; - } else { - float tav = (tvb + tvt) / 2.0f; - - //System.out.println("tav = " + tav); - //System.out.println("RKAP * tav = " + RKAP * tav); - return (RKAP * tav); - } - } - } - - /* - * Compute virtual temperature - */ - private float virtualTemperature(float tt, float td, float pres) { - if (tt == RMISSD || pres == RMISSD) { - return RMISSD; - } else if (td == RMISSD) { - return celciusToKevin(tt); - } else { - float tmpk = celciusToKevin(tt); - float rmix = mixingRatio(td, pres); - if (rmix == RMISSD) { - return celciusToKevin(tt); - } else { - return tmpk * (1.f + .001f * rmix / .62197f) - / (1.f + .001f * rmix); - } - } - } - - /* - * Convert Celcius to Kelvin. - */ - float TMCK = 273.15f; - - private float celciusToKevin(float tc) { - if (tc == RMISSD) { - return RMISSD; - } else { - return (tc + TMCK); - } - } - - /* - * Compute mixing ratio from DWPC and PRES. - */ - private float mixingRatio(float td, float pres) { - if (td == RMISSD || pres == RMISSD) { - return RMISSD; - } else { - float vapr = vaporPressure(td); - if (vapr == RMISSD) { - return RMISSD; - } - - float corr = (1.001f + ((pres - 100.f) / 900.f) * .0034f); - - float e = corr * vapr; - if (e > (.5f * pres)) { - return RMISSD; - } else { - return .62197f * (e / (pres - e)) * 1000.f; - } - } - } - - /* - * Compute vapor pressure from DWPC. - */ - private float vaporPressure(float td) { - if (td == RMISSD) { - return RMISSD; - } else { - return (6.112f * (float) Math.exp((17.67 * td) / (td + 243.5))); - } - } - - /* - * Merge observed sounding data - */ - - /* - * Check significant wind data (PPBB/PPDD) to see if the data is reported on - * pressure or height surfaces. Return TRUE if reported on height. The - * input can be PPBB or PPDD winds. (MR_CHKW) - * - * Note that this is coded different from MR_CHKW, in that it will set zwind - * to false only if pressure is less than 0. An odd logic. - */ - public boolean checkWindData(List sndata) { - boolean zwind = true; - for (int kk = 0; kk < sndata.size(); kk++) { - if (sndata.get(kk).getPressure() != RMISSD) { - zwind = false; - } - } - return zwind; - } - - /* - * Find surface data. (MR_SRFC) - */ - final int NPARMS = 7; - - - public NcSoundingLayer getSurfaceData(List man, - List ttbb, List ppbb, float elevation ) { - float pres = RMISSD; - NcSoundingLayer sl_sfc = new NcSoundingLayer(); - - /* - * Check for surface information in mandatory data. - */ - if (man.size() < 1) { - for (int k = 0; k < NPARMS; k++) { - sl_sfc.setPressure(RMISSD); - sl_sfc.setTemperature(RMISSD); - sl_sfc.setDewpoint(RMISSD); - sl_sfc.setWindDirection(RMISSD); - sl_sfc.setWindSpeed(RMISSD); - sl_sfc.setGeoHeight(RMISSD); - sl_sfc.setOmega(RMISSD); - return sl_sfc; - } - } else { - - // If surface pressure is higher than 1080mb, set to missing. - // Note that GEMPAK sets it to 1060mb. - pres = man.get(0).getPressure(); - if (pres > 1080.) { - sl_sfc.setPressure(RMISSD); - } else { - sl_sfc.setPressure(pres); - } - sl_sfc.setTemperature(man.get(0).getTemperature()); - sl_sfc.setDewpoint(man.get(0).getDewpoint()); - sl_sfc.setWindDirection(man.get(0).getWindDirection()); - sl_sfc.setWindSpeed(man.get(0).getWindSpeed()); - sl_sfc.setGeoHeight(elevation); - sl_sfc.setOmega(man.get(0).getOmega()); - } - - /* - * Find the first reporting mandatory level above the surface. - */ - float pman = RMISSD; - int iman = 1; - while (pman == RMISSD && iman < man.size()) { - if (man.get(iman).getPressure() != RMISSD - && man.get(iman).getTemperature() != RMISSD - && man.get(iman).getGeoHeight() != RMISSD) { - pman = man.get(0).getPressure(); - } - iman++; - } - - /* - * If surface pressure is missing or is less than first reporting - * mandatory level, set all surface data to missing - */ - if (pres == RMISSD || (pres < pman && pman != RMISSD)) { - sl_sfc = setMissing().get(0); - } - - /* - * If the surface significant temperature data is not missing, use it to - * replace the surface pressure, temperature and dewpoint. The check for - * significant level pressure to be less than or equal to pman eliminates - * using wildly erroneous data. - */ - - if (ttbb.size() >= 1 && ttbb.get(0).getPressure() != RMISSD - && ttbb.get(0).getTemperature() != RMISSD) { - pman = sl_sfc.getPressure(); - float psql = ttbb.get(0).getPressure(); - if (pman == RMISSD || equal(pman, psql)) { - sl_sfc.setPressure(ttbb.get(0).getPressure()); - sl_sfc.setTemperature(ttbb.get(0).getTemperature()); - sl_sfc.setDewpoint(ttbb.get(0).getDewpoint()); - } - - } - - /* - * If the first significant level wind data is surface information, use - * it to replace the surface data if the pressure is at surface. - */ - - // PPBB reported on P-surfaces. - - if (checkWindData(ppbb)) { - if (ppbb.size() > 0 && ppbb.get(0).getGeoHeight() == 0. - && ppbb.get(0).getWindDirection() != RMISSD) { - sl_sfc.setWindDirection(ppbb.get(0).getWindDirection()); - sl_sfc.setWindSpeed(ppbb.get(0).getWindSpeed()); - } - - } else { - if (ppbb.size() > 0 && ppbb.get(0).getPressure() != RMISSD - && ppbb.get(0).getWindDirection() != RMISSD) { - pman = sl_sfc.getPressure(); - float psgl = Math.abs(ppbb.get(0).getPressure()); - if (pman == RMISSD || equal(pman, psgl)) { - sl_sfc.setPressure(psgl); - sl_sfc.setWindDirection(ppbb.get(0).getWindDirection()); - sl_sfc.setWindSpeed(ppbb.get(0).getWindSpeed()); - } - } - } - - /* - * Add surface data to station data. - */ - return sl_sfc; - } - - - private boolean equal(float x, float y) { - final float RDIFFD = .0001f; - if ( (x + y) == 0. ) { - return Math.abs(x-y) < RDIFFD; - } else { - return Math.abs(x - y) / Math.abs( (x+y) / 2.) < RDIFFD; - } - } - - /* - * Merge the mandatory below 100 mb data and the mandatory above data. sndata - * has surface observation ONLY. - * (MR_MAND) - */ - - public void mergeMandatory( - List man_a, List man_c, - List sndata ) { - - float plast; - if ( man_a.size() < 1 && man_c.size() < 1 ) { - return; - } - - if ( sndata.get(0).getPressure() == RMISSD) { - plast = 2000.f; - } else { - plast = sndata.get(0).getPressure(); - } - - /* - * Move the mandatory data below 100mb to the output array, sndata. - * Check that pressure is not missing and is decreasing. - */ - float pres; - if ( man_a.size() > 0 ) { - for (int kk = 0; kk < man_a.size(); kk++) { - pres = man_a.get(kk).getPressure(); - if (pres < plast && pres != RMISSD - && ( man_a.get(kk).getTemperature() != RMISSD - || man_a.get(kk).getWindDirection() != RMISSD)) { - addDataToList(kk, man_a, sndata); - plast = pres; - } - } - } - - /* - * Move the mandatory data above 100 mb to the output array. - */ - if ( man_c.size() > 0 ) { - for (int kk = 0; kk < man_c.size(); kk++) { - pres = man_c.get(kk).getPressure(); - if (pres < plast && pres != RMISSD - && man_c.get(kk).getTemperature() != RMISSD) { - addDataToList(kk, man_c, sndata); - plast = man_c.get(kk).getPressure(); - } - } - } - } - - /* - * Merge the mandatory below 100 mb wind data and the mandatory above wind - * data. (MR_MANW) - */ - public void mergeMandatoryWinds( List man_wa, - List man_wc, List sndata ) { - - if ( man_wa.size() < 1 && man_wc.size() < 1 ) { - return; - } - - /* - * Append data. - */ - if (man_wc.size() > 0) { - for (int kk = 0; kk < man_wc.size(); kk++) { - man_wa.add(man_wc.get(kk)); - } - } - /* - * Loop through mandatory wind data. - */ - for (int lev = 0; lev < man_wa.size(); lev++) { - - /* - * If this is the correct level, add wind data. - */ - boolean found = false; - float ppp = man_wa.get(lev).getPressure(); - for (int kk = 0; kk < sndata.size() && !found; kk++ ) { - float pres = sndata.get(kk).getPressure(); - if (equal(ppp,pres)) { - if (sndata.get(kk).getWindDirection() == RMISSD) { - sndata.get(kk).setWindDirection(man_wa.get(lev).getWindDirection()); - sndata.get(kk).setWindSpeed(man_wa.get(lev).getWindSpeed()); - } - found = true; - } - } - - /* - * If not found, add to the list - */ - if (!found) { - float ddd = man_wa.get(lev).getWindDirection(); - if (ppp != RMISSD && ddd != RMISSD) { - addDataToList(lev,man_wa,sndata); - } - } - } - } - - /* - * Merge tropopause, max wind and significant temperature data (TTBB) to the - * station data array. The input parameter could be tropopause data or - * significant temperature data. MR_TROP & MR_SIGT - */ - public List mergeTropSigTemp( List trop_a, - List trop_c, List sndata) { - if ( trop_a.size() < 1 && trop_c.size() < 1 ) { - return sndata; - } - - /* - * Append two lists of wind data. - */ - if ( trop_c.size() > 0 ) { - for (int kk = 0; kk < trop_c.size(); kk++) { - trop_a.add(trop_c.get(kk)); - } - } - - for (int lev = 0; lev < trop_a.size(); lev++) { - boolean found = false; - float ppp = trop_a.get(lev).getPressure(); - for (int kk = 0; kk < sndata.size() && !found; kk++) { - float pres = sndata.get(kk).getPressure(); - if (equal(ppp ,pres)) { - - // add data to missing - if (sndata.get(kk).getTemperature() == RMISSD) { - sndata.get(kk).setTemperature(trop_a.get(lev).getTemperature()); - sndata.get(kk).setDewpoint(trop_a.get(lev).getDewpoint()); - } - - if (sndata.get(kk).getWindDirection() == RMISSD) { - sndata.get(kk).setWindDirection( - trop_a.get(lev).getWindDirection()); - sndata.get(kk).setWindSpeed( - trop_a.get(lev).getWindSpeed()); - - } - found = true; - } - } - - /* - * if not found, add to the list - */ - if (!found) { - float ttt = trop_a.get(lev).getTemperature(); - if (ppp != RMISSD && ttt != RMISSD) { - addDataToList(lev,trop_a,sndata); - } - } - - } - - /* - * Sort the sounding data in descending order. - */ - Collections.sort(sndata, new reverseSortByPressure()); - return sndata; - } - - /* - * Compute height at significant temperature levels (TTBB) using a moist - * hydrostatic computation. (MR_SCMZ) - */ - public void constructTtbbHeight(List sndata) { - boolean mand = false; - boolean newblock = true; - int blev = 0, tlev = 0; - float[] scale = new float[200]; - float pb, zb, tb, tdb, zlev, plev; - float pt, zt = 0.f, tt, tdt, znew = 0.f; - - if ( sndata.size() <= 2) return; - - for (int nlev = 0; nlev < sndata.size(); nlev++) { -// //System.out.println("blev = " + blev ); - //System.out.println("nlev = " + nlev ); -// //System.out.println("tlev = " + tlev ); - if (newblock) { - if (sndata.get(nlev).getGeoHeight() != RMISSD - && sndata.get(nlev).getPressure() != RMISSD - && sndata.get(nlev).getTemperature() != RMISSD) { - - blev = nlev; - newblock = false; - // double h = sndata.get(nlev).getGeoHeight(); -// if ( h < 0 && h != -9999 ) - //System.out.println("if newblock - height is negative: " + h ); - } - } else { - if (sndata.get(nlev).getGeoHeight() != RMISSD - && sndata.get(nlev).getTemperature() != RMISSD) { - tlev = nlev; - mand = true; -//System.out.println("tlev is now set to nlev and its value is " + tlev ); - // double h = sndata.get(nlev).getGeoHeight(); - // if ( h < 0 && h != -9999 ) - //System.out.println("if not newblock - height is negative: " + h ); - } - } - - /* - * Compute scale height to this level - */ - if (mand) { - pb = sndata.get(blev).getPressure(); - zb = sndata.get(blev).getGeoHeight(); - tb = sndata.get(blev).getTemperature(); - tdb = sndata.get(blev).getDewpoint(); - zlev = sndata.get(blev).getGeoHeight(); - plev = sndata.get(blev).getPressure(); - - for (int kk = blev + 1; kk <= tlev; kk++) { - pt = sndata.get(kk).getPressure(); - zt = sndata.get(kk).getGeoHeight(); - tt = sndata.get(kk).getTemperature(); - tdt = sndata.get(kk).getDewpoint(); - scale[kk] = scaleHeight(tb, tt, tdb, tdt, pb, pt); - //System.out.println("scale[" + kk + "] = " + scale[kk]); - znew = moistHeight(zb, pb, pt, scale[kk]); - if (znew != RMISSD) { - pb = pt; - tb = tt; - tdb = tdt; - zb = znew; -// if ( znew < 0 ) - //System.out.println("negative moist height = " + znew ); - } - } - - /* - * Compute the scaling factor so the computed moist height is - * consistent at the mandatory level. Then recompute the height. - */ - float s = (zt - zlev) / (znew - zlev); - //System.out.println("scaling factor s = " + s); - float zbb = zlev; - float pbb = plev; - for (int kk = blev + 1; kk < tlev; kk++) { - pt = sndata.get(kk).getPressure(); - zt = sndata.get(kk).getGeoHeight(); - scale[kk] = scale[kk] * s; - //System.out.println("Now, scale[" + kk + "] = " + scale[kk]); - znew = moistHeight(zbb, pbb, pt, scale[kk]); - if (znew != RMISSD) { - pbb = pt; - zbb = znew; - sndata.get(kk).setGeoHeight(znew); -// double h = sndata.get(kk).getGeoHeight(); -// if ( h < 0 && h != RMISSD ) - //System.out.println("newly computed moist height is negative " + h ); - } - } - mand = false; - newblock = true; - - if ( (tlev+1) != sndata.size() ) { - if ( sndata.get(tlev+1).getGeoHeight() == RMISSD - && sndata.get(tlev+1).getPressure() != RMISSD - && sndata.get(tlev+1).getTemperature() != RMISSD) { - nlev--; - //System.out.println("after subtracting nlev, its value is : " + nlev ); - } - } - } - } - - // Compute height at the missing top levels - - if ( ( tlev + 1 ) < sndata.size() ) { - blev = tlev; - pb = sndata.get(blev).getPressure(); - zb = sndata.get(blev).getGeoHeight(); - tb = sndata.get(blev).getTemperature(); - tdb = sndata.get(blev).getDewpoint(); - zlev = sndata.get(blev).getGeoHeight(); - plev = sndata.get(blev).getPressure(); - for ( int kk = tlev+1; kk < sndata.size(); kk++ ) { - pt = sndata.get(kk).getPressure(); - zt = sndata.get(kk).getGeoHeight(); - tt = sndata.get(kk).getTemperature(); - tdt = sndata.get(kk).getDewpoint(); - float xxx = scaleHeight(tb, tt, tdb, tdt, pb, pt); - znew = moistHeight(zb, pb, pt, xxx); - if (znew != RMISSD) { - sndata.get(kk).setGeoHeight(znew); - pb = pt; - tb = tt; - tdb = tdt; - zb = znew; - } - } - } - - return; - } - - /* - * Merge the significant and maximum wind data on pressure surfaces. MR_PWND - */ - public void mergeSigMaxWindOnPressure(List sig_wa, - List sig_wc,List sndata) { - - /* - * Do nothing if wind report is reported on height surfaces. - */ - - if ( sig_wa.size() < 1 && sig_wc.size() < 1 ) { - return; - } - - /* - * Append two lists of wind data. - */ - if ( sig_wc.size() > 0) { - for (int kk = 0; kk < sig_wc.size(); kk++) { - sig_wa.add(sig_wc.get(kk)); - } - } - - /* - * Merging - */ - int nlevel = sndata.size(); - for (int kk = 0; kk < sig_wa.size(); kk++) { - boolean found = false; - for (int lev = 0; lev < nlevel; lev++) { - if (equal(sndata.get(lev).getPressure(),sig_wa.get(kk) - .getPressure())) { - - // add data to missing - if (sndata.get(lev).getWindDirection() == RMISSD) { - sndata.get(lev).setWindDirection( - sig_wa.get(kk).getWindDirection()); - sndata.get(lev).setWindSpeed( - sig_wa.get(kk).getWindSpeed()); - } - found = true; - } - } - - /* - * if not found, add to the list. - */ - if (!found) { - if ( ( sig_wa.get(kk).getWindDirection() != RMISSD - && sig_wa.get(kk).getPressure() != RMISSD )) { - - NcSoundingLayer sl = new NcSoundingLayer(); - sl.setPressure(sig_wa.get(kk).getPressure()); - sl.setTemperature(sig_wa.get(kk).getTemperature()); - sl.setDewpoint(sig_wa.get(kk).getDewpoint()); - sl.setWindDirection(sig_wa.get(kk).getWindDirection()); - sl.setWindSpeed(sig_wa.get(kk).getWindSpeed()); - sl.setGeoHeight(sig_wa.get(kk).getGeoHeight()); - sl.setOmega(sig_wa.get(kk).getOmega()); - sndata.add(sl); - } - } - Collections.sort(sndata, new reverseSortByPressure()); - } - return; - } - - /* - * Construct height at significant wind levels (PPBB) if reported on - * pressure levels. Using moist hydrostatic computation for missing height - * at top levels. MR_INTZ - */ - public void constructPpbbHeight(List sndata) { - int tlev = 0; -//System.out.println("From constructPpbbHeight(): " ); - for (int kk = sndata.size() - 1; kk >= 0; kk--) { - if (sndata.get(kk).getGeoHeight() != RMISSD) { - tlev = kk; - //System.out.println("tlev is set to " + tlev ); - break; - } - } - - float pb = RMISSD, pt = RMISSD, zt = RMISSD, zb = RMISSD; - int next; - - if ( sndata.size() <= 2) return; - - for (int kk = 0; kk < tlev; kk++) { - float pres = sndata.get(kk).getPressure(); - float hght = sndata.get(kk).getGeoHeight(); - if (pres == RMISSD) { - // DO NOTHING - } else if (hght != RMISSD) { - pb = pres; - zb = hght; - pt = 2000.f; - } else if (pb == RMISSD) { - // DO NOTHING - } else { - - /* - * Find next level with height and then interpolate the data. - */ - next = kk + 1; - while (pres <= pt) { - if (sndata.get(next).getGeoHeight() != RMISSD) { - pt = sndata.get(next).getPressure(); - zt = sndata.get(next).getGeoHeight(); - } else { - next++; - } - } - float hhh = (float) (zb + (zt - zb) - * (Math.log(pres / pb) / Math.log(pt / pb))); - sndata.get(kk).setGeoHeight(hhh); - } - } - - if (tlev == (sndata.size()-1) ) { - return; - } else { - - /* - * Compute moist hydrostatic height for missing height at top - * levels. - */ - float scale; - float tb, tdb; - float tt, tdt, mhght = 0.f; - pb = sndata.get(tlev).getPressure(); - zb = sndata.get(tlev).getGeoHeight(); - tb = sndata.get(tlev).getTemperature(); - tdb = sndata.get(tlev).getDewpoint(); - - for (int kk = tlev+1; kk < sndata.size(); kk++) { - if (sndata.get(kk).getGeoHeight() == RMISSD) { - pt = sndata.get(kk).getPressure(); - zt = sndata.get(kk).getGeoHeight(); - tt = sndata.get(kk).getTemperature(); - tdt = sndata.get(kk).getDewpoint(); - scale = scaleHeight(tb, tt, tdb, tdt, pb, pt); - mhght = moistHeight(zb, pb, pt, scale); - sndata.get(kk).setGeoHeight(mhght); - if (mhght != RMISSD) { - pb = pt; - zb = zt; - tb = tt; - tdb = tdt; - } - } - } - return; - } - } - - /* - * Merge significant wind on height surfaces. The argument is sndata. - */ - public void mergeSigWindOnHeight(List sig_wa, - List sig_wc,List sndata ) { - - /* - * The following code needs to be replaced by significant wind data from - * database. - */ - - /* - * Do nothing if wind report is not on height surfaces. - */ - if ( sig_wa.size() < 1 && sig_wc.size() < 1 ) { - return; - } - - /* - * Add two lists of wind data. - */ - if ( sig_wc.size() > 0 ) { - for (int kk = 0; kk < sig_wc.size(); kk++) { - sig_wa.add(sig_wc.get(kk)); - } - } - - int nlevel = sndata.size(); - for (int kk = 0; kk < sig_wa.size(); kk++) { - boolean found = false; - float zzz = sig_wa.get(kk).getGeoHeight(); - - // Check surface level independently because sometimes station - // report wind data twice at surface. We don't want the surface - // pressure to be missing. - if ( zzz == 0 ) { - if (sndata.get(0).getWindDirection() == RMISSD) { - sndata.get(0).setWindDirection( - sig_wa.get(0).getWindDirection()); - sndata.get(0).setWindSpeed( - sig_wa.get(kk).getWindSpeed()); - } - found = true; - } else { - for (int lev = 0; lev < nlevel; lev++) { - float hght = sndata.get(lev).getGeoHeight(); - if (equal(zzz,hght) || (zzz == 0 && lev == 0 && kk == 0)) { - // add data to missing - if (sndata.get(lev).getWindDirection() == RMISSD) { - sndata.get(lev).setWindDirection( - sig_wa.get(kk).getWindDirection()); - sndata.get(lev).setWindSpeed( - sig_wa.get(kk).getWindSpeed()); - } - found = true; - } - } - } - - /* - * if not found, add to the list. - */ - if (!found) { - if (sig_wa.get(kk).getWindDirection() != RMISSD - && sig_wa.get(kk).getGeoHeight() != RMISSD) { - NcSoundingLayer sl = new NcSoundingLayer(); - sl.setPressure(sig_wa.get(kk).getPressure()); - sl.setTemperature(sig_wa.get(kk).getTemperature()); - sl.setDewpoint(sig_wa.get(kk).getDewpoint()); - sl.setWindDirection(sig_wa.get(kk).getWindDirection()); - sl.setWindSpeed(sig_wa.get(kk).getWindSpeed()); - sl.setGeoHeight(sig_wa.get(kk).getGeoHeight()); - sl.setOmega(sig_wa.get(kk).getOmega()); - sndata.add(sl); - } - } - } - - /* - * Sorting the combined temperature and wind data based on Geopotential - * height. - */ - Collections.sort(sndata, new sortByHeight()); - return; - } - - // Sort by height - public static class sortByHeight implements Comparator { - public int compare(NcSoundingLayer l1, NcSoundingLayer l2) { - return Float.compare(l1.getGeoHeight(), l2.getGeoHeight()); - } - - } - - // Reverse sort by pressure - public static class reverseSortByPressure implements Comparator { - public int compare(NcSoundingLayer l1, NcSoundingLayer l2) { - return Float.compare(l2.getPressure(), l1.getPressure()); - } - - } - - /* - * Construct pressure at significant wind levels (PPBB) that are reported on - * height levels. MR_INTP - */ - public List constructPpbbPressure(List sndata) { - - if ( sndata.size() <= 2) return sndata; - - float pb = RMISSD, pt = RMISSD, zt = RMISSD, zb = RMISSD; - int blev = IMISSD, tlev = IMISSD; - for (int lev = 0; lev < sndata.size(); lev++) { - float pres = sndata.get(lev).getPressure(); - float hght = sndata.get(lev).getGeoHeight(); - if (pres != RMISSD && hght != RMISSD) { - tlev = lev; - pt = pres; - zt = hght; - } - - if (blev != IMISSD && tlev != IMISSD) { - for (int kk = blev + 1; kk < tlev; kk++) { - float z = sndata.get(kk).getGeoHeight(); - if (sndata.get(kk).getGeoHeight() != RMISSD) { - float ppp = (float) (pb * Math.exp((double) ((z - zb) - * Math.log(pt / pb) / (zt - zb)))); - sndata.get(kk).setPressure(ppp); - } - } - } - blev = tlev; - pb = pt; - zb = zt; - } - - ////System.out.println ( " tlev: " + tlev + " sndata.size() -1 " + (sndata.size()-1)); - - if (tlev == (sndata.size()-1) || tlev == IMISSD ) { - return sndata; - } else { - - /* - * Compute missing pressure at top - * levels. - */ - pb = sndata.get(tlev-1).getPressure(); - zb = sndata.get(tlev-1).getGeoHeight(); - - for (int kk = tlev+1; kk < sndata.size(); kk++) { - if (sndata.get(kk).getPressure() == RMISSD) { - pt = sndata.get(kk-1).getPressure(); - zt = sndata.get(kk-1).getGeoHeight(); - float zz = sndata.get(kk).getGeoHeight(); - float rmult = (float) ((zz- zb) / (zt - zb)); - sndata.get(kk).setPressure((float)(pb*(Math.pow(pt/pb,rmult)))); - pb = pt; - zb = zt; - } - } - } - return sndata; - } - - - - /* - * Reorder the sounding data so the first level is always the surface data. - */ - public List reOrderSounding(List sndata) { - List outdat = new ArrayList(); - float tt, td, dd, ff; - int klev = 0; - if ( sndata.size() <= 1) return sndata; - - /* - * Find the surface level - */ - for (int kk = 0; kk < sndata.size(); kk++) { - tt = sndata.get(kk).getTemperature(); - td = sndata.get(kk).getDewpoint(); - dd = sndata.get(kk).getWindDirection(); - ff = sndata.get(kk).getWindSpeed(); - if (tt == RMISSD && td == RMISSD && dd == RMISSD && ff == RMISSD) { - // DO NOTHING - } else { - klev = kk; - addDataToList(0, sndata, outdat); - } - } - - /* - * Reorder the data below the surface levels. - */ - for (int kk = 0; kk < klev; kk++) { - addDataToList(kk, sndata, outdat); - } - - for (int kk = klev + 1; kk < sndata.size(); kk++) { - addDataToList(kk, sndata, outdat); - } - return outdat; - } - - /* - * Construct missing temperature (iflag = 1), dewpoint temperature (iflag=2) - * and wind (iflag = 3). This method is called after reOrderSounding(). - * MR_MISS - */ - public List constructMissing(int iflag, - List sndata) { - float pb = RMISSD, pt = RMISSD, data = RMISSD, pres, tb, tt, tdb, tdt; - int jlev = IMISSD, tlev = IMISSD; - boolean contin = true; - if ( sndata.size() <= 2) return sndata; - for (int blev = 1; blev < sndata.size() - 1 && contin; blev++) { - jlev = blev; - - switch (iflag) { - case 1: { - data = sndata.get(blev).getTemperature(); - break; - } - case 2: { - data = sndata.get(blev).getDewpoint(); - break; - } - case 3: { - data = sndata.get(blev).getWindDirection(); - break; - } - default: { - //System.out.println("Invalid data flag"); - return sndata; - } - } - - if (data == RMISSD) { - - /* - * find data at level above. Data should already be at level - * below after reOrderSounding() call. - */ - boolean found = false; - while (!found) { - jlev++; - switch (iflag) { - case 1: { - data = sndata.get(jlev).getTemperature(); - break; - } - case 2: { - data = sndata.get(jlev).getDewpoint(); - break; - } - case 3: { - data = sndata.get(jlev).getWindDirection(); - break; - } - default: { - //System.out.println("Invalid data flag"); - } - } - int top = sndata.size(); - if (data != RMISSD || jlev+1 >= top) { - found = true; - tlev = jlev; - if (jlev >= top) { - tlev = IMISSD; - contin = false; - } - } - } - - /* - * Add check to eliminate dew point layer more than 100mb. - */ - if (iflag == 2 && tlev != IMISSD) { - if ((sndata.get(blev).getPressure() - sndata.get(tlev) - .getPressure()) > 100.) { - for (int kk = tlev; kk < sndata.size(); kk++) { - sndata.get(kk).setDewpoint(RMISSD); - } - tlev = IMISSD; - contin = false; - } - } - - /* - * Add check to eliminate interpolation of winds from below 100 - * mb to above 100 mb. This eliminates interpolation to very - * high level winds. - */ - /* if (iflag == 3 && tlev != IMISSD - && (sndata.get(blev - 1).getPressure() > 100.) - && (sndata.get(tlev).getPressure() < 100.)) { - tlev = IMISSD; - } - */ - /* - * Interpolate with respect to logP. - */ - - if (tlev != IMISSD) { - pb = sndata.get(blev - 1).getPressure(); - pres = sndata.get(blev).getPressure(); - pt = sndata.get(tlev).getPressure(); - float rmult = (float) (Math.log(pres / pb) / Math.log(pt / pb)); - switch (iflag) { - case 1: { - tb = sndata.get(blev - 1).getTemperature(); - tt = sndata.get(tlev).getTemperature(); - if ( tb != RMISSD && tt != RMISSD ) { - data = tb + (tt - tb) * rmult; - sndata.get(blev).setTemperature(data); - } - - tdb = sndata.get(blev - 1).getDewpoint(); - tdt = sndata.get(tlev).getDewpoint(); - if (tdb != RMISSD && tdt != RMISSD) { - data = tdb + (tdt - tdb) * rmult; - sndata.get(blev).setDewpoint(data); - } - break; - } - case 2: { - tdb = sndata.get(blev - 1).getDewpoint(); - tdt = sndata.get(tlev).getDewpoint(); - if (tdb != RMISSD && tdt != RMISSD) { - data = tdb + (tdt - tdb) * rmult; - sndata.get(blev).setDewpoint(data); - } - break; - } - case 3: { - float drctb = sndata.get(blev - 1).getWindDirection() ; - float drctt = sndata.get(tlev).getWindDirection() ; - - if ( drctt != RMISSD && drctb!= RMISSD ) { - drctb = drctb % 360.f; - drctt = drctt % 360.f; - if (Math.abs(drctb - drctt) > 180.f) { - if (drctb < drctt) { - drctb = drctb + 360.f; - } else { - drctt = drctt + 360.f; - } - } - float drct = ( drctb + (drctt - drctb) * rmult) % 360.f; - sndata.get(blev).setWindDirection(drct); - - // Interpolate wind speed - float spedb = sndata.get(blev - 1).getWindSpeed(); - float spedt = sndata.get(tlev).getWindSpeed(); - float sped = spedb + (spedt - spedb) * rmult; - sndata.get(blev).setWindSpeed(sped); - } - break; - - } - } - } - } - } - return sndata; - } - - - /* - * Re-order the data so the first level is always the ground level. MR_COND - */ - public List addUnderGround(List man, - List sndata ) { - - if (sndata.get(0).getPressure() == RMISSD || man.size() < 1 || sndata.size() <= 1) { - return sndata; - } - - int blev = 0; - boolean contin = true; - while ( blev < sndata.size() && contin ) { - if (man.get(blev).getPressure() > sndata.get(0).getPressure() ) { - blev++; - } else { - contin = false; - } - } - - if ( blev >= sndata.size()) { - return sndata; - } - - /* - * Added below-ground mandatory levels to sounding layers. - */ - List outdat = new ArrayList(); - - int nlev = sndata.size(); - - // write to surface data first - addDataToList (0, sndata, outdat); - - // add below-ground mandatory data - if (blev > 0 && blev < sndata.size()) { - for (int kk = 0; kk < blev; kk++ ) { - addDataToList(kk,man,outdat); - } - } - - // add the rest of the data - for (int kk = 1; kk < nlev; kk++) { - addDataToList(kk,sndata,outdat); - } - return outdat; - - } - - /* - * Re-order the data so the first level is always the ground level. MR_COND - */ - public List removeUnderGround(List sndata) { - List outdat = new ArrayList(); - /* - * Remove below-ground mandatory levels from sounding layers. Only the - * first 8 missing levels can be mandatory levels. - */ - if ( sndata.size() <= 1) return sndata; - for ( int kk = 0; kk < sndata.size(); kk++ ) { - if ( sndata.get(kk).getTemperature() <= RMISSD && - sndata.get(kk).getDewpoint() <= RMISSD && - sndata.get(kk).getWindDirection() <= RMISSD && - sndata.get(kk).getWindSpeed() <= RMISSD ) { - } else if ( sndata.get(kk).getPressure() <= RMISSD ) { - } else { - addDataToList(kk, sndata, outdat); - } - } - return outdat; - } - - /* - * Interpolate data to a single level, including surface. - */ - public List getSingLevel (float pres, List sndata) { - NcSoundingLayer sl = new NcSoundingLayer(); - List sls = new ArrayList(); - sndata = removeUnderGround(sndata); - if ( sndata.size() <= 1) return setMissing(); //Chin: check size again, after remove under ground lavel, size changed - - for ( int kk = 1;/*chin 0;*/ kk < sndata.size()-1; kk++ ) { - if (pres > sndata.get(0).getPressure()|| pres < 0.f) { - return setMissing(); - } else { - - if ( pres >= sndata.get(kk).getPressure()) { - float pt, pb, zt, zb, tt, tb, tdt, tdb, dt, db, st, sb; - pb = sndata.get(kk-1).getPressure(); - pt = sndata.get(kk).getPressure(); - tb = sndata.get(kk-1).getTemperature(); - tt = sndata.get(kk).getTemperature(); - tdb = sndata.get(kk-1).getDewpoint(); - tdt = sndata.get(kk).getDewpoint(); - db = sndata.get(kk-1).getWindDirection() % 360.f; - dt = sndata.get(kk).getWindDirection() % 360.f; - sb = sndata.get(kk-1).getWindSpeed(); - st = sndata.get(kk).getWindSpeed(); - zb = sndata.get(kk-1).getGeoHeight(); - zt = sndata.get(kk).getGeoHeight(); - sl.setPressure(pres); - - float rmult = (float) (Math.log(pres / pb) / Math.log(pt/ pb)); - sl.setTemperature(tb+(tt-tb)*rmult); - sl.setDewpoint(tdb+(tdt-tdb)*rmult); - if (Math.abs(db-dt) > 180.) { - if ( db < dt ) { - db = db + 360.f; - } else { - dt = dt + 360.f; - } - } - sl.setWindDirection(db+(dt-db)*rmult); - sl.setWindSpeed(sb+(st-sb)*rmult); - sl.setGeoHeight(zb+(zt-zb)*rmult); - sls.add(sl); - return sls; - } - } - } - return setMissing(); - } - - /* - * Add data to output sounding profile. - */ - public void addDataToList(int index, - List indat, List outdat) { - NcSoundingLayer sl = new NcSoundingLayer(); - sl.setPressure(indat.get(index).getPressure()); - sl.setTemperature(indat.get(index).getTemperature()); - sl.setDewpoint(indat.get(index).getDewpoint()); - sl.setWindDirection(indat.get(index).getWindDirection()); - sl.setWindSpeed(indat.get(index).getWindSpeed()); - sl.setGeoHeight(indat.get(index).getGeoHeight()); - sl.setOmega(indat.get(index).getOmega()); - outdat.add(sl); - } - - /* - * Set missing to output sounding profile. - */ - public List setMissing () { - List outdat = new ArrayList(); - NcSoundingLayer sl = new NcSoundingLayer(); - sl.setPressure(RMISSD); - sl.setTemperature(RMISSD); - sl.setDewpoint(RMISSD); - sl.setWindDirection(RMISSD); - sl.setWindSpeed(RMISSD); - sl.setGeoHeight(RMISSD); - sl.setOmega(RMISSD); - outdat.add(sl); - return outdat; - } + final float RMISSD = IDecoderConstantsN.UAIR_FLOAT_MISSING; + + final int IMISSD = IDecoderConstantsN.INTEGER_MISSING; + + /** + * Default constructor + */ + public MergeSounding() { + } + + /* + * Process native sounding data. Convert specific humidity to dew point + * temperature then compute the moist height. + */ + public List nativeModelSounding(List sls, + float elevation) { + spfhToDewpoint(sls); + constructHeight(sls, elevation); + return sls; + } + + /* + * Process upper air sounding data. Note that TTAA is the original/sorted + * data, while MAN is the TTAA without underground data and MAN_D is the + * TTAA for display, i.e., the first level is the surface level, any under + * -ground levels will be above the surface level. + */ + public List mergeUairSounding(String level, + List ttaa, List ttbb, + List ttcc, List ttdd, + List ppaa, List ppbb, + List ppcc, List ppdd, + List trop_a, List trop_c, + List wmax_a, List wmax_c, + float elevation) { + List sndata = new ArrayList(); + List man = null; + System.out.println("Enter mergeUairSounding....."); + + // Return the specific levels requested by users + if (ttaa.size() > 0) { + Collections.sort(ttaa, new reverseSortByPressure()); + // System.out.println(" TTAA sounding: "); + // printOut(ttaa); + + if (level.toUpperCase().equalsIgnoreCase("MAN")) { + return ttaa; + } + man = removeUnderGround(ttaa); + // System.out.println(" TTAA sounding above ground: "); + // printOut(man); + + } else { + if (ppaa.size() < 1 && ttbb.size() < 1) { + System.out + .println(" Missing TTAA/TTBB and PPAA data."); + return missingSounding(); + } else { + man = missingSounding(); + System.out.println(" Missing Mandatory (TTAA) Data!"); + } + } + + // Sorting the data + + if (ttbb.size() > 0) { + Collections.sort(ttbb, new reverseSortByPressure()); + // System.out.println(" TTBB sounding: "); + // printOut(ttbb); + } + + if (ttcc.size() > 0) { + Collections.sort(ttcc, new reverseSortByPressure()); + // System.out.println(" TTCC sounding: "); + // printOut(ttcc); + + } + + if (ttdd.size() > 0) { + Collections.sort(ttdd, new reverseSortByPressure()); + // System.out.println(" TTDD sounding: "); + // printOut(ttdd); + + } + + if (ppaa.size() > 0) { + if (checkWindData(ppaa)) { + Collections.sort(ppaa, new MergeSounding.sortByHeight()); + // System.out.println(" PPAA sounding by Height: "); + // printOut(ppaa); + + } else { + Collections.sort(ppaa, + new MergeSounding.reverseSortByPressure()); + // System.out.println(" PPAA sounding by Pressure: "); + // printOut(ppaa); + + } + } + + if (ppcc.size() > 0) { + if (checkWindData(ppcc)) { + Collections.sort(ppcc, new MergeSounding.sortByHeight()); + // System.out.println(" PPCC sounding by Height:"); + // printOut(ppcc); + + } else { + Collections.sort(ppcc, + new MergeSounding.reverseSortByPressure()); + // System.out.println(" PPCC sounding by Pressure: "); + // printOut(ppcc); + } + } + + if (ppbb.size() > 0) { + if (checkWindData(ppbb)) { + Collections.sort(ppbb, new MergeSounding.sortByHeight()); + // System.out.println(" PPBB sounding by Height:"); + // printOut(ppbb); + + } else { + Collections.sort(ppbb, + new MergeSounding.reverseSortByPressure()); + // System.out.println(" PPBB sounding by Pressure: "); + // printOut(ppbb); + } + } + + if (ppdd.size() > 0) { + if (checkWindData(ppdd)) { + Collections.sort(ppdd, new MergeSounding.sortByHeight()); + // System.out.println(" PPDD sounding by Height"); + // printOut(ppdd); + } else { + Collections.sort(ppdd, + new MergeSounding.reverseSortByPressure()); + // System.out.println(" PPDD sounding by Pressure: "); + // printOut(ppdd); + } + + } + + // Find surface data, return if users request surface data only. + NcSoundingLayer sl = new NcSoundingLayer(); + sl = getSurfaceData(man, ttbb, ppbb, elevation); + sndata.add(0, sl); + // System.out.println(" surface data "); + // printOut(sndata); + + if (isNumber(level) >= 0) { + if (equal(0.f, Float.valueOf(level.trim()).floatValue()) + || equal(sl.getPressure(), Float.valueOf(level.trim()) + .floatValue())) { + return sndata; + } + } + + // Merge mandatory data + mergeMandatory(man, ttcc, sndata); + // System.out.println("after merge Mandatory data: "); + // printOut(sndata); + + // Check if the single level is mandatory or not + if (isNumber(level) >= 0) { + for (int kk = 0; kk < sndata.size(); kk++) { + if (equal(Float.valueOf(level.trim()).floatValue(), + sndata.get(kk).getPressure())) { + sl.setPressure(sndata.get(kk).getPressure()); + sl.setTemperature(sndata.get(kk).getTemperature()); + sl.setDewpoint(sndata.get(kk).getDewpoint()); + sl.setWindDirection(sndata.get(kk).getWindDirection()); + sl.setWindSpeed(sndata.get(kk).getWindSpeed()); + sl.setGeoHeight(sndata.get(kk).getGeoHeight()); + sl.setOmega(sndata.get(kk).getOmega()); + sndata.clear(); + sndata.add(sl); + return sndata; + } + } + } + + // Merge mandatory winds + mergeMandatoryWinds(ppaa, ppcc, sndata); + // System.out.println("after merge PPAA/PPCC: "); + // printOut(sndata); + + // Merge tropopause + mergeTropSigTemp(trop_a, trop_c, sndata); + // System.out.println("after merge Tropause data: "); + // printOut(sndata); + + // Merge TTBB + mergeTropSigTemp(ttbb, ttdd, sndata); + // System.out.println("after merge TTBB/TTDD: "); + // printOut(sndata); + + constructTtbbHeight(sndata); + // System.out.println("after construct TTBB Height: "); + // printOut(sndata); + + if (!checkWindData(ppbb)) { + mergeSigMaxWindOnPressure(ppbb, ppdd, sndata); + // System.out.println("after merge TTBB/TTDD: "); + // printOut(sndata); + + } + + mergeSigMaxWindOnPressure(wmax_a, wmax_c, sndata); + // System.out.println("after merge max wind: "); + // printOut(sndata); + + constructPpbbHeight(sndata); + // System.out.println(" After contrstruct PPBB height: "); + // printOut(sndata); + + if (checkWindData(ppbb)) { + mergeSigWindOnHeight(ppbb, ppdd, sndata); + constructPpbbPressure(sndata); + // System.out.println("after merge PPBB/PPDD: "); + // printOut(sndata); + } + + // Reorder sounding profile so the first level is the surface data. + // No need to reorder now becuz surface data is always the 1st level. + // reOrderSounding(sndata); + + // Interpolate missing temperature, dew point and winds. + constructMissing(1, sndata); + constructMissing(2, sndata); + + // constructMissing(3,sndata); + // System.out.println("after contrstruct Missing data: "); + // printOut(sndata); + + // Return single level or add underground mandatory data to the sounding + // profile + + List sndout = new ArrayList(); + sndout = removeMissingPressure(sndata); + // System.out.println(" Missing pressure removed "); + // printOut(sndout); + if (isNumber(level) == 0) { + float rlev = new Integer(Integer.parseInt(level.trim())) + .floatValue(); + return getSingLevel(rlev, sndout); + } else if (isNumber(level) == 1) { + float rlev = new Float(Float.parseFloat(level.trim())); + return getSingLevel(rlev, sndout); + } else { + if (sndout.size() < 2) + System.out.println(" Not enough levels to plot the sounding! "); + return addUnderGround(ttaa, sndout); + } + } + + /* + * Check an alpha-numerical string is a number or characters. + */ + public int isNumber(String level) { + try { + if (Integer.parseInt(level) >= 0) { + return 0; + } else { + return -1; + } + } catch (NumberFormatException nfe1) { + try { + if (Float.parseFloat(level) >= 0.f) { + return 1; + } else { + return -1; + } + } catch (NumberFormatException nfe2) { + try { + if (Double.parseDouble(level) >= 0.) { + return 2; + } else { + return -1; + } + } catch (NumberFormatException nfe3) { + return -1; + } + } + } + } + + /* + * convert specific humidity to dew point temperature. + */ + float elevation; + + public List spfhToDewpoint(List sndata) { + float spfh, pres; + float dwpc = RMISSD; + + for (NcSoundingLayer layer : sndata) { + if (layer.getDewpoint() == RMISSD) { + spfh = layer.getSpecHumidity(); + pres = layer.getPressure(); + + if (spfh == RMISSD || pres == RMISSD || spfh <= 0.f + || pres <= 0.f) { + continue; + } else { + float rmix = spfh / (1.f - spfh); + float e = (pres * rmix) / (.62197f + rmix); + e = e / (1.001f + ((pres - 100.f) / 900.f) * .0034f); + dwpc = (float) (Math.log(e / 6.112) * 243.5 / (17.67 - Math + .log((e / 6.112)))); + layer.setDewpoint(dwpc); + // //System.out.println("spfhToDewpoint dwpc: " + dwpc); + } + + } + } + return sndata; + } + + /* + * computes DWPC from TMPC and RELH Note: If DWPC is less than -190 degrees + * C, it is treated as missing data Code is based on GEMPAK's prrhdp.f + */ + public List rhToDewpoint(List sndata) { + float rh, vapr, vaps, temp; + float dwpc = RMISSD; + + for (NcSoundingLayer layer : sndata) { + if (layer.getDewpoint() == RMISSD) { + rh = layer.getRelativeHumidity(); + temp = layer.getTemperature(); + + if (rh == RMISSD || temp == RMISSD) { + continue; + } else { + vaps = tempToVapr(temp); + vapr = rh * vaps / 100; + if (vapr < Math.exp(-30)) + continue; + else { + dwpc = (float) (243.5 * (Math.log(6.112) - Math + .log(vapr)) / (Math.log(vapr) - Math.log(6.112) - 17.67)); + layer.setDewpoint(dwpc); + // //System.out.println("rhToDewpoint dwpc: " + dwpc); + } + } + } + } + return sndata; + } + + /* + * computes VAPR from TMPC Code is based on GEMPAK's prvapr.f + */ + private float tempToVapr(float temp) { + return (float) (6.112 * Math.exp((17.67 * temp) / (temp + 243.5))); + } + + private void constructHeight(List sndata, float elev) { + + /* + * For native model sounding, using hypsometric equation to build height + */ + elevation = elev; + int lev = sndata.size(); + float tb = RMISSD, tdb = RMISSD, pb = RMISSD; + float tt = RMISSD, tdt = RMISSD, pt = RMISSD; + float dwptsf, psfc, tmpcsf, scaleh, mhgt = RMISSD; + + for (int k = 0; k < lev; k++) { + + if (k == 0) { + tmpcsf = sndata.get(k).getTemperature(); + dwptsf = sndata.get(k).getDewpoint(); + psfc = sndata.get(k).getPressure(); + tb = tmpcsf; + tt = tmpcsf; + tdb = dwptsf; + tdt = dwptsf; + pb = psfc; + pt = psfc; + + scaleh = scaleHeight(tb, tt, tdb, tdt, pb, pt); + mhgt = moistHeight(elevation, pb, pt, scaleh); + } else { + tt = sndata.get(k).getTemperature(); + tdt = sndata.get(k).getDewpoint(); + pt = sndata.get(k).getPressure(); + scaleh = scaleHeight(tb, tt, tdb, tdt, pb, pt); + + mhgt = moistHeight(mhgt, pb, pt, scaleh); + tb = tt; + tdb = tdt; + pb = pt; + + } + sndata.get(k).setGeoHeight(mhgt); + } + } + + /* + * Compute moist height. + */ + private float moistHeight(float zb, float pb, float pt, float scale) { + // System.out.println("From moistHeight: "); + if (zb == RMISSD || pb == RMISSD || pt == RMISSD || scale == RMISSD) { + return RMISSD; + } else { + // System.out.println("the computed moistHeight is " + (float) (zb + // + scale * Math.log(pb / pt))); + return (float) (zb + scale * Math.log(pb / pt)); + } + } + + /* + * Compute scale height. + */ + private float scaleHeight(float tb, float tt, float tdb, float tdt, + float pb, float pt) { + // System.out.println("From scaleHeight: " ); + final float RDGAS = 287.04f, GRAVTY = 9.80616f, RKAP = RDGAS / GRAVTY; + if (tb == RMISSD || tt == RMISSD || pb == RMISSD || pt == RMISSD) { + return RMISSD; + } else { + float tvb = virtualTemperature(tb, tdb, pb); + float tvt = virtualTemperature(tt, tdt, pt); + // System.out.println("tvb = " + tvb); + // System.out.println("tvt = " + tvt); + if (tvb == RMISSD || tvt == RMISSD) { + return RMISSD; + } else { + float tav = (tvb + tvt) / 2.0f; + + // System.out.println("tav = " + tav); + // System.out.println("RKAP * tav = " + RKAP * tav); + return (RKAP * tav); + } + } + } + + /* + * Compute virtual temperature + */ + private float virtualTemperature(float tt, float td, float pres) { + if (tt == RMISSD || pres == RMISSD) { + return RMISSD; + } else if (td == RMISSD) { + return celciusToKevin(tt); + } else { + float tmpk = celciusToKevin(tt); + float rmix = mixingRatio(td, pres); + if (rmix == RMISSD) { + return celciusToKevin(tt); + } else { + return tmpk * (1.f + .001f * rmix / .62197f) + / (1.f + .001f * rmix); + } + } + } + + /* + * Convert Celcius to Kelvin. + */ + float TMCK = 273.15f; + + private float celciusToKevin(float tc) { + if (tc == RMISSD) { + return RMISSD; + } else { + return (tc + TMCK); + } + } + + /* + * Compute mixing ratio from DWPC and PRES. + */ + private float mixingRatio(float td, float pres) { + if (td == RMISSD || pres == RMISSD) { + return RMISSD; + } else { + float vapr = vaporPressure(td); + if (vapr == RMISSD) { + return RMISSD; + } + + float corr = (1.001f + ((pres - 100.f) / 900.f) * .0034f); + + float e = corr * vapr; + if (e > (.5f * pres)) { + return RMISSD; + } else { + return .62197f * (e / (pres - e)) * 1000.f; + } + } + } + + /* + * Compute vapor pressure from DWPC. + */ + private float vaporPressure(float td) { + if (td == RMISSD) { + return RMISSD; + } else { + return (6.112f * (float) Math.exp((17.67 * td) / (td + 243.5))); + } + } + + /* + * Merge observed sounding data + */ + + /* + * Check wind data if the data is reported on pressure or height surfaces. + * Return TRUE if reported on height. (MR_CHKW) + * + * Note that this is coded different from MR_CHKW, in that it will set zwind + * to false only if pressure is less than 0. An odd logic. + */ + public boolean checkWindData(List sndata) { + boolean zwind = true; + for (int kk = 0; kk < sndata.size(); kk++) { + if (sndata.get(kk).getPressure() != RMISSD) { + zwind = false; + } + } + return zwind; + } + + /* + * Find surface data. (MR_SRFC) + */ + final int NPARMS = 7; + + public NcSoundingLayer getSurfaceData(List man, + List ttbb, List ppbb, + float elevation) { + float psfc = RMISSD; + NcSoundingLayer sl_sfc = new NcSoundingLayer(); + + /* + * Check for surface information in mandatory data. + */ + if (man == null || man.size() < 1) { + sl_sfc = missingSounding().get(0); + } else { + // If surface pressure is greater than 1080mb, set to missing. + // Otherwise + // surface pressure will be the first report level on TTAA. + // Note that GEMPAK sets it to 1060mb. + psfc = man.get(0).getPressure(); + if (psfc > 1080.) { + sl_sfc.setPressure(RMISSD); + } else { + sl_sfc.setPressure(psfc); + } + sl_sfc.setTemperature(man.get(0).getTemperature()); + sl_sfc.setDewpoint(man.get(0).getDewpoint()); + sl_sfc.setWindDirection(man.get(0).getWindDirection()); + sl_sfc.setWindSpeed(man.get(0).getWindSpeed()); + sl_sfc.setGeoHeight(elevation); + sl_sfc.setOmega(man.get(0).getOmega()); + } + + /* + * Find the first reporting mandatory level above the surface. + */ + float pman = RMISSD; + int iman = 1; + try { + while (pman == RMISSD && iman < man.size()) { + if (man.get(iman).getPressure() != RMISSD + && man.get(iman).getTemperature() != RMISSD + && man.get(iman).getGeoHeight() != RMISSD) { + pman = man.get(iman).getPressure(); + } + iman++; + } + } catch (Exception e) { + // do nothing + } + + /* + * If surface pressure is missing or is less than first reporting + * mandatory level, set surface data to missing. + */ + if (psfc == RMISSD || (psfc < pman && pman != RMISSD)) { + sl_sfc = missingSounding().get(0); + } + + /* + * Use TTBB/PPBB to get surface data if TTAA is missing. The check for + * significant level pressure to be less than or equal to psfc + * eliminates erroneous data. + */ + if (ttbb.size() > 0) { + psfc = sl_sfc.getPressure(); + float psql = ttbb.get(0).getPressure(); + + if (equal(psfc, psql) || psfc == RMISSD) { + if (psql != RMISSD && psfc == RMISSD) + sl_sfc.setPressure(psql); + + if (sl_sfc.getTemperature() == RMISSD) + sl_sfc.setTemperature(ttbb.get(0).getTemperature()); + + if (sl_sfc.getDewpoint() == RMISSD) + sl_sfc.setDewpoint(ttbb.get(0).getDewpoint()); + } + } + + /* + * If the first significant level wind data is surface information, use + * it to replace the surface data if the pressure is at surface. + */ + + // PPBB reported in Height. + if (checkWindData(ppbb)) { + if (ppbb.size() > 0) { + if (ppbb.get(0).getGeoHeight() == 0. + && sl_sfc.getWindDirection() == RMISSD) { + sl_sfc.setWindDirection(ppbb.get(0).getWindDirection()); + sl_sfc.setWindSpeed(ppbb.get(0).getWindSpeed()); + } + } + } else { + // PPBB reported in Pressure. + if (ppbb.size() > 0) { + if (ppbb.get(0).getPressure() != RMISSD + && sl_sfc.getPressure() == RMISSD) { + float psgl = Math.abs(ppbb.get(0).getPressure()); + sl_sfc.setPressure(psgl); + + if (ppbb.get(0).getWindDirection() != RMISSD + && sl_sfc.getWindDirection() == RMISSD) { + if (equal(psfc, psgl)) { + sl_sfc.setWindDirection(ppbb.get(0) + .getWindDirection()); + sl_sfc.setWindSpeed(ppbb.get(0).getWindSpeed()); + } + } + } + } + } + + /* + * Return surface data. + */ + return sl_sfc; + } + + private boolean equal(float x, float y) { + final float RDIFFD = .0001f; + if ((x + y) == 0.) { + return Math.abs(x - y) < RDIFFD; + } else { + return Math.abs(x - y) / Math.abs((x + y) / 2.) < RDIFFD; + } + } + + /* + * Merge the mandatory below 100 mb data and the mandatory above data. + * sndata has surface observation ONLY. (MR_MAND) + */ + + public void mergeMandatory(List man_a, + List man_c, List sndata) { + + float plast; + // if (man_a == null) { + // System.out.println(" NO data in Man \n"); + // } else { + // System.out.println(" Man not null \n"); + + // + if (man_a.size() < 1 && man_c.size() < 1) { + return; + } + + if (sndata.get(0).getPressure() == RMISSD) { + plast = 2000.f; + } else { + plast = sndata.get(0).getPressure(); + } + + /* + * Move the mandatory data below 100mb to the output array, sndata. + * Check that pressure is not missing and is decreasing. + */ + float pres; + if (man_a.size() > 0) { + for (int kk = 1; kk < man_a.size(); kk++) { + pres = man_a.get(kk).getPressure(); + if (pres < plast + && pres != RMISSD + && (man_a.get(kk).getTemperature() != RMISSD || man_a + .get(kk).getWindDirection() != RMISSD)) { + addDataToList(kk, man_a, sndata); + plast = pres; + } + } + } + + /* + * Move the mandatory data above 100 mb to the output array. + */ + if (man_c.size() > 0) { + for (int kk = 0; kk < man_c.size(); kk++) { + pres = man_c.get(kk).getPressure(); + if (pres < plast && pres != RMISSD + && man_c.get(kk).getTemperature() != RMISSD) { + addDataToList(kk, man_c, sndata); + plast = man_c.get(kk).getPressure(); + } + } + } + } + + /* + * Merge the mandatory below 100 mb wind data and the mandatory above wind + * data. (MR_MANW) + */ + public void mergeMandatoryWinds(List man_wa, + List man_wc, List sndata) { + + if (man_wa.size() < 1 && man_wc.size() < 1) { + return; + } + + /* + * Append data. + */ + if (man_wc.size() > 0) { + for (int kk = 0; kk < man_wc.size(); kk++) { + man_wa.add(man_wc.get(kk)); + } + } + /* + * Loop through mandatory wind data. + */ + for (int lev = 0; lev < man_wa.size(); lev++) { + + /* + * If this is the correct level, add wind data. + */ + boolean found = false; + float ppp = man_wa.get(lev).getPressure(); + for (int kk = 0; kk < sndata.size() && !found; kk++) { + float pres = sndata.get(kk).getPressure(); + if (equal(ppp, pres)) { + if (sndata.get(kk).getWindDirection() == RMISSD) { + sndata.get(kk).setWindDirection( + man_wa.get(lev).getWindDirection()); + sndata.get(kk).setWindSpeed( + man_wa.get(lev).getWindSpeed()); + } + found = true; + } + } + + /* + * If not found, add to the list + */ + if (!found) { + float ddd = man_wa.get(lev).getWindDirection(); + if (ppp != RMISSD && ddd != RMISSD) { + addDataToList(lev, man_wa, sndata); + } + } + } + } + + /* + * Merge tropopause, max wind and significant temperature data (TTBB) to the + * station data array. The input parameter could be tropopause data or + * significant temperature data. MR_TROP & MR_SIGT + */ + public List mergeTropSigTemp(List trop_a, + List trop_c, List sndata) { + if (trop_a.size() < 1 && trop_c.size() < 1) { + return sndata; + } + + /* + * Append two lists of wind data. + */ + if (trop_c.size() > 0) { + for (int kk = 0; kk < trop_c.size(); kk++) { + trop_a.add(trop_c.get(kk)); + } + } + + for (int lev = 0; lev < trop_a.size(); lev++) { + boolean found = false; + float ppp = trop_a.get(lev).getPressure(); + for (int kk = 0; kk < sndata.size() && !found; kk++) { + float pres = sndata.get(kk).getPressure(); + if (equal(ppp, pres)) { + + // add data to missing + if (sndata.get(kk).getTemperature() == RMISSD) { + sndata.get(kk).setTemperature( + trop_a.get(lev).getTemperature()); + sndata.get(kk).setDewpoint( + trop_a.get(lev).getDewpoint()); + } + + if (sndata.get(kk).getWindDirection() == RMISSD) { + sndata.get(kk).setWindDirection( + trop_a.get(lev).getWindDirection()); + sndata.get(kk).setWindSpeed( + trop_a.get(lev).getWindSpeed()); + + } + found = true; + } + } + + /* + * if not found, add to the list + */ + if (!found) { + float ttt = trop_a.get(lev).getTemperature(); + if (ppp != RMISSD && ttt != RMISSD) { + addDataToList(lev, trop_a, sndata); + } + } + + } + + /* + * Sort the sounding data in descending order. + */ + Collections.sort(sndata, new reverseSortByPressure()); + return sndata; + } + + /* + * Compute height at significant temperature levels (TTBB) using a moist + * hydrostatic computation. (MR_SCMZ) + */ + public void constructTtbbHeight(List sndata) { + boolean mand = false; + boolean newblock = true; + int blev = 0, tlev = 0; + float[] scale = new float[200]; + float pb, zb, tb, tdb, zlev, plev; + float pt, zt = 0.f, tt, tdt, znew = 0.f; + // System.out.println("In construct TTBB Height 1: "); + // printOut(sndata); + + if (sndata.size() <= 2) + return; + + for (int nlev = 0; nlev < sndata.size(); nlev++) { + if (newblock) { + if (sndata.get(nlev).getGeoHeight() != RMISSD + && sndata.get(nlev).getPressure() != RMISSD + && sndata.get(nlev).getTemperature() != RMISSD) { + + blev = nlev; + newblock = false; + } + } else { + if (sndata.get(nlev).getGeoHeight() != RMISSD + && sndata.get(nlev).getTemperature() != RMISSD) { + tlev = nlev; + mand = true; + } + } + + /* + * Compute scale height to this level + */ + if (mand) { + pb = sndata.get(blev).getPressure(); + zb = sndata.get(blev).getGeoHeight(); + tb = sndata.get(blev).getTemperature(); + tdb = sndata.get(blev).getDewpoint(); + zlev = sndata.get(blev).getGeoHeight(); + plev = sndata.get(blev).getPressure(); + + for (int kk = blev + 1; kk <= tlev; kk++) { + pt = sndata.get(kk).getPressure(); + zt = sndata.get(kk).getGeoHeight(); + tt = sndata.get(kk).getTemperature(); + tdt = sndata.get(kk).getDewpoint(); + scale[kk] = scaleHeight(tb, tt, tdb, tdt, pb, pt); + znew = moistHeight(zb, pb, pt, scale[kk]); + if (znew != RMISSD) { + pb = pt; + tb = tt; + tdb = tdt; + zb = znew; + } + } + + /* + * Compute the scaling factor so the computed moist height is + * consistent at the mandatory level. Then recompute the height. + */ + float s = (zt - zlev) / (znew - zlev); + float zbb = zlev; + float pbb = plev; + for (int kk = blev + 1; kk < tlev; kk++) { + pt = sndata.get(kk).getPressure(); + zt = sndata.get(kk).getGeoHeight(); + scale[kk] = scale[kk] * s; + znew = moistHeight(zbb, pbb, pt, scale[kk]); + if (znew != RMISSD) { + pbb = pt; + zbb = znew; + sndata.get(kk).setGeoHeight(znew); + } + } + mand = false; + newblock = true; + + if ((tlev + 1) != sndata.size()) { + if (sndata.get(tlev + 1).getGeoHeight() == RMISSD + && sndata.get(tlev + 1).getPressure() != RMISSD + && sndata.get(tlev + 1).getTemperature() != RMISSD) { + nlev--; + } + } + } + } + // System.out + // .println("In construct TTBB Height before check TOP missing level: "); + // printOut(sndata); + + // System.out.println(" TLEV: " + tlev + " sndata.size()" + + // sndata.size() + "\n"); + // Fill missing height at the top levels + fillMissingHeightAtTop(sndata); + + return; + } + + /* + * Merge the significant and maximum wind data on pressure surfaces. MR_PWND + */ + public void mergeSigMaxWindOnPressure(List sig_wa, + List sig_wc, List sndata) { + + if (sig_wa.size() < 1 && sig_wc.size() < 1) { + return; + } + + // System.out.println(" Windmax below trop \n"); + // printOut(sig_wa); + // System.out.println(" Windmax above trop \n"); + // printOut(sig_wc); + + /* + * Append two lists of wind data. + */ + if (sig_wc.size() > 0 && sig_wc.get(0).getPressure() != RMISSD) { + for (int kk = 0; kk < sig_wc.size(); kk++) { + sig_wa.add(sig_wc.get(kk)); + } + } + + /* + * Merging + */ + int nlevel = sndata.size(); + for (int kk = 0; kk < sig_wa.size(); kk++) { + boolean found = false; + for (int lev = 0; lev < nlevel; lev++) { + if (equal(sndata.get(lev).getPressure(), sig_wa.get(kk) + .getPressure())) { + + // add data to missing + if (sndata.get(lev).getWindDirection() == RMISSD) { + sndata.get(lev).setWindDirection( + sig_wa.get(kk).getWindDirection()); + sndata.get(lev).setWindSpeed( + sig_wa.get(kk).getWindSpeed()); + } + found = true; + } + } + // System.out.println("In mergeSigMaxWindOnPressure:); + // printOut(sndata); + + /* + * if not found, add to the list. + */ + if (!found) { + if ((sig_wa.get(kk).getWindDirection() != RMISSD && sig_wa.get( + kk).getPressure() != RMISSD)) { + + NcSoundingLayer sl = new NcSoundingLayer(); + sl.setPressure(sig_wa.get(kk).getPressure()); + sl.setTemperature(sig_wa.get(kk).getTemperature()); + sl.setDewpoint(sig_wa.get(kk).getDewpoint()); + sl.setWindDirection(sig_wa.get(kk).getWindDirection()); + sl.setWindSpeed(sig_wa.get(kk).getWindSpeed()); + sl.setGeoHeight(sig_wa.get(kk).getGeoHeight()); + sl.setOmega(sig_wa.get(kk).getOmega()); + sndata.add(sl); + nlevel++; + } + } + + Collections.sort(sndata, new reverseSortByPressure()); + } + fillMissingHeightAtTop(sndata); + // printOut(sndata); + return; + } + + /* + * Construct height at significant wind levels (PPBB) if reported on + * pressure levels. Using moist hydrostatic computation for missing height + * at top levels. MR_INTZ + */ + public void constructPpbbHeight(List sndata) { + int tlev = 0; + // System.out.println("From constructPpbbHeight(): " ); + for (int kk = sndata.size() - 1; kk >= 0; kk--) { + if (sndata.get(kk).getGeoHeight() != RMISSD) { + tlev = kk; + break; + } + } + + float pb = RMISSD, pt = RMISSD, zt = RMISSD, zb = RMISSD; + int next; + + if (sndata.size() <= 2) + return; + + for (int kk = 0; kk < tlev; kk++) { + float pres = sndata.get(kk).getPressure(); + float hght = sndata.get(kk).getGeoHeight(); + if (pres == RMISSD) { + // DO NOTHING + } else if (hght != RMISSD) { + pb = pres; + zb = hght; + pt = 2000.f; + } else if (pb == RMISSD) { + // DO NOTHING + } else { + + /* + * Find next level with height and then interpolate the data. + */ + next = kk + 1; + while (pres <= pt) { + if (sndata.get(next).getGeoHeight() != RMISSD) { + pt = sndata.get(next).getPressure(); + zt = sndata.get(next).getGeoHeight(); + } else { + next++; + } + } + float hhh = (float) (zb + (zt - zb) + * (Math.log(pres / pb) / Math.log(pt / pb))); + sndata.get(kk).setGeoHeight(hhh); + } + } + + fillMissingHeightAtTop(sndata); + } + + /* + * Merge significant wind on height surfaces. The argument is sndata. + */ + public void mergeSigWindOnHeight(List sig_wa, + List sig_wc, List sndata) { + + /* + * The following code needs to be replaced by significant wind data from + * database. + */ + + /* + * Do nothing if wind report is not on height surfaces. + */ + if (sig_wa.size() < 1 && sig_wc.size() < 1) { + return; + } + + /* + * Add two lists of wind data. + */ + if (sig_wc.size() > 0) { + for (int kk = 0; kk < sig_wc.size(); kk++) { + sig_wa.add(sig_wc.get(kk)); + } + } + // System.out.println(" in mergeSigWindOnHeight 0: "); + // printOut(sndata); + + int nlevel = sndata.size(); + for (int kk = 0; kk < sig_wa.size(); kk++) { + boolean found = false; + float zzz = sig_wa.get(kk).getGeoHeight(); + + // Check surface level independently because sometimes station + // report wind data twice at surface. We don't want the surface + // pressure to be missing. + if (zzz == 0) { + if (sndata.get(0).getWindDirection() == RMISSD) { + sndata.get(0).setWindDirection( + sig_wa.get(0).getWindDirection()); + sndata.get(0).setWindSpeed(sig_wa.get(kk).getWindSpeed()); + } + found = true; + } else { + for (int lev = 0; lev < nlevel; lev++) { + float hght = sndata.get(lev).getGeoHeight(); + if (equal(zzz, hght) || (zzz == 0 && lev == 0 && kk == 0)) { + // add data to missing + if (sndata.get(lev).getWindDirection() == RMISSD) { + sndata.get(lev).setWindDirection( + sig_wa.get(kk).getWindDirection()); + sndata.get(lev).setWindSpeed( + sig_wa.get(kk).getWindSpeed()); + } + found = true; + } + } + } + + // System.out.println(" in mergeSigWindOnHeight 1: "); + // printOut(sndata); + + /* + * if not found, add to the list. + */ + if (!found) { + if (sig_wa.get(kk).getWindDirection() != RMISSD + && sig_wa.get(kk).getGeoHeight() != RMISSD) { + NcSoundingLayer sl = new NcSoundingLayer(); + sl.setPressure(sig_wa.get(kk).getPressure()); + sl.setTemperature(sig_wa.get(kk).getTemperature()); + sl.setDewpoint(sig_wa.get(kk).getDewpoint()); + sl.setWindDirection(sig_wa.get(kk).getWindDirection()); + sl.setWindSpeed(sig_wa.get(kk).getWindSpeed()); + sl.setGeoHeight(sig_wa.get(kk).getGeoHeight()); + sl.setOmega(sig_wa.get(kk).getOmega()); + sndata.add(sl); + } + } + } + + /* + * Sorting the combined temperature and wind data based on Geopotential + * height. + */ + // System.out.println(" in mergeSigWindOnHeight 2: "); + // printOut(sndata); + + fillMissingHeightAtTop(sndata); + + Collections.sort(sndata, new sortByHeight()); + return; + } + + // Sort by height + public static class sortByHeight implements Comparator { + public int compare(NcSoundingLayer l1, NcSoundingLayer l2) { + return Float.compare(l1.getGeoHeight(), l2.getGeoHeight()); + } + + } + + // Reverse sort by pressure + public static class reverseSortByPressure implements + Comparator { + public int compare(NcSoundingLayer l1, NcSoundingLayer l2) { + return Float.compare(l2.getPressure(), l1.getPressure()); + } + + } + + /* + * Construct pressure at significant wind levels (PPBB) that are reported on + * height levels. MR_INTP + */ + public List constructPpbbPressure( + List sndata) { + + if (sndata.size() <= 2) + return sndata; + + float pb = RMISSD, pt = RMISSD, zt = RMISSD, zb = RMISSD; + int blev = IMISSD, tlev = IMISSD; + for (int lev = 0; lev < sndata.size(); lev++) { + float pres = sndata.get(lev).getPressure(); + float hght = sndata.get(lev).getGeoHeight(); + if (pres != RMISSD && hght != RMISSD) { + tlev = lev; + pt = pres; + zt = hght; + } + + if (blev != IMISSD && tlev != IMISSD) { + for (int kk = blev + 1; kk < tlev; kk++) { + float z = sndata.get(kk).getGeoHeight(); + if (sndata.get(kk).getGeoHeight() != RMISSD) { + float ppp = (float) (pb * Math.exp((double) ((z - zb) + * Math.log(pt / pb) / (zt - zb)))); + sndata.get(kk).setPressure(ppp); + } + } + } + blev = tlev; + pb = pt; + zb = zt; + } + + if (tlev == (sndata.size() - 1) || tlev == IMISSD) { + return sndata; + } else { + + /* + * Compute missing pressure at top levels. + */ + pb = sndata.get(tlev - 1).getPressure(); + zb = sndata.get(tlev - 1).getGeoHeight(); + + for (int kk = tlev + 1; kk < sndata.size(); kk++) { + if (sndata.get(kk).getPressure() == RMISSD) { + pt = sndata.get(kk - 1).getPressure(); + zt = sndata.get(kk - 1).getGeoHeight(); + float zz = sndata.get(kk).getGeoHeight(); + float rmult = (float) ((zz - zb) / (zt - zb)); + sndata.get(kk).setPressure( + (float) (pb * (Math.pow(pt / pb, rmult)))); + pb = pt; + zb = zt; + } + } + } + return sndata; + } + + /* + * Reorder the sounding data so the first level is always the surface data. + */ + public List reOrderSounding(List sndata) { + List outdat = new ArrayList(); + float tt, td, dd, ff; + int klev = 0; + if (sndata.size() <= 1) + return sndata; + + /* + * Find the surface level + */ + for (int kk = 0; kk < sndata.size(); kk++) { + tt = sndata.get(kk).getTemperature(); + td = sndata.get(kk).getDewpoint(); + dd = sndata.get(kk).getWindDirection(); + ff = sndata.get(kk).getWindSpeed(); + if (tt == RMISSD && td == RMISSD && dd == RMISSD && ff == RMISSD) { + // DO NOTHING + } else { + klev = kk; + addDataToList(0, sndata, outdat); + } + } + + /* + * Reorder the data below the surface levels. + */ + for (int kk = 0; kk < klev; kk++) { + addDataToList(kk, sndata, outdat); + } + + for (int kk = klev + 1; kk < sndata.size(); kk++) { + addDataToList(kk, sndata, outdat); + } + return outdat; + } + + /* + * Construct missing temperature (iflag = 1), dewpoint temperature (iflag=2) + * and wind (iflag = 3). This method is called after reOrderSounding(). + * MR_MISS + */ + public List constructMissing(int iflag, + List sndata) { + float pb = RMISSD, pt = RMISSD, data = RMISSD, pres, tb, tt, tdb, tdt; + int jlev = IMISSD, tlev = IMISSD; + boolean contin = true; + if (sndata.size() <= 2) + return sndata; + for (int blev = 1; blev < sndata.size() - 1 && contin; blev++) { + jlev = blev; + + switch (iflag) { + case 1: { + data = sndata.get(blev).getTemperature(); + break; + } + case 2: { + data = sndata.get(blev).getDewpoint(); + break; + } + case 3: { + data = sndata.get(blev).getWindDirection(); + break; + } + default: { + return sndata; + } + } + + if (data == RMISSD) { + + /* + * find data at level above. Data should already be at level + * below after reOrderSounding() call. + */ + boolean found = false; + while (!found) { + jlev++; + switch (iflag) { + case 1: { + data = sndata.get(jlev).getTemperature(); + break; + } + case 2: { + data = sndata.get(jlev).getDewpoint(); + break; + } + case 3: { + data = sndata.get(jlev).getWindDirection(); + break; + } + default: { + // System.out.println("Invalid data flag"); + } + } + int top = sndata.size(); + if (data != RMISSD || jlev + 1 >= top) { + found = true; + tlev = jlev; + if (jlev >= top) { + tlev = IMISSD; + contin = false; + } + } + } + + /* + * Add check to eliminate dew point layer more than 100mb. + */ + if (iflag == 2 && tlev != IMISSD) { + if ((sndata.get(blev).getPressure() - sndata.get(tlev) + .getPressure()) > 100.) { + for (int kk = tlev; kk < sndata.size(); kk++) { + sndata.get(kk).setDewpoint(RMISSD); + } + tlev = IMISSD; + contin = false; + } + } + + /* + * Add check to eliminate interpolation of winds from below 100 + * mb to above 100 mb. This eliminates interpolation to very + * high level winds. + */ + /* + * if (iflag == 3 && tlev != IMISSD && (sndata.get(blev - + * 1).getPressure() > 100.) && (sndata.get(tlev).getPressure() < + * 100.)) { tlev = IMISSD; } + */ + /* + * Interpolate with respect to logP. + */ + float rmult = RMISSD; + if (tlev != IMISSD) { + pb = sndata.get(blev - 1).getPressure(); + pres = sndata.get(blev).getPressure(); + pt = sndata.get(tlev).getPressure(); + if (pt != RMISSD && pb != RMISSD && pres != RMISSD) { + rmult = (float) (Math.log(pres / pb) / Math + .log(pt / pb)); + } + + switch (iflag) { + case 1: { + tb = sndata.get(blev - 1).getTemperature(); + tt = sndata.get(tlev).getTemperature(); + + if (tb != RMISSD && tt != RMISSD && rmult != RMISSD) { + data = tb + (tt - tb) * rmult; + + sndata.get(blev).setTemperature(data); + } + + tdb = sndata.get(blev - 1).getDewpoint(); + tdt = sndata.get(tlev).getDewpoint(); + if (tdb != RMISSD && tdt != RMISSD && rmult != RMISSD) { + data = tdb + (tdt - tdb) * rmult; + sndata.get(blev).setDewpoint(data); + } + break; + } + case 2: { + tdb = sndata.get(blev - 1).getDewpoint(); + tdt = sndata.get(tlev).getDewpoint(); + if (tdb != RMISSD && tdt != RMISSD && rmult != RMISSD) { + data = tdb + (tdt - tdb) * rmult; + sndata.get(blev).setDewpoint(data); + } + break; + } + case 3: { + float drctb = sndata.get(blev - 1).getWindDirection(); + float drctt = sndata.get(tlev).getWindDirection(); + + if (drctt != RMISSD && drctb != RMISSD + && rmult != RMISSD) { + drctb = drctb % 360.f; + drctt = drctt % 360.f; + if (Math.abs(drctb - drctt) > 180.f) { + if (drctb < drctt) { + drctb = drctb + 360.f; + } else { + drctt = drctt + 360.f; + } + } + float drct = (drctb + (drctt - drctb) * rmult) % 360.f; + sndata.get(blev).setWindDirection(drct); + + // Interpolate wind speed + float spedb = sndata.get(blev - 1).getWindSpeed(); + float spedt = sndata.get(tlev).getWindSpeed(); + float sped = spedb + (spedt - spedb) * rmult; + sndata.get(blev).setWindSpeed(sped); + } + break; + + } + } + } + } + } + return sndata; + } + + /* + * Re-order the data so the first level is always the ground level. MR_COND + */ + public List addUnderGround(List man, + List sndata) { + if (sndata == null || sndata.size() < 2) + return sndata; + if (sndata.get(0).getPressure() == RMISSD || man.size() < 1) + return sndata; + + int blev = 0; + boolean contin = true; + while (blev < sndata.size() && contin) { + if (man.get(blev).getPressure() > sndata.get(0).getPressure()) { + blev++; + if (blev >= man.size()) + contin = false; + } else { + contin = false; + } + } + + if (blev >= sndata.size()) { + return sndata; + } + + /* + * Added below-ground mandatory levels to sounding layers. + */ + List outdat = new ArrayList(); + + int nlev = sndata.size(); + + // write to surface data first + addDataToList(0, sndata, outdat); + + // add below-ground mandatory data + if (blev > 0 && blev < sndata.size()) { + for (int kk = 0; kk < blev; kk++) { + addDataToList(kk, man, outdat); + } + } + + // add the rest of the data + for (int kk = 1; kk < nlev; kk++) { + addDataToList(kk, sndata, outdat); + } + return outdat; + + } + + /* + * Re-order the data so the first level is always the ground level. MR_COND + */ + public List removeUnderGround(List sndata) { + List outdat = new ArrayList(); + /* + * Remove below-ground mandatory levels from sounding layers. Only the + * first 8 missing levels can be mandatory levels. + */ + if (sndata.size() <= 1) + return sndata; + for (int kk = 0; kk < sndata.size(); kk++) { + if (sndata.get(kk).getTemperature() <= RMISSD + && sndata.get(kk).getDewpoint() <= RMISSD + && sndata.get(kk).getWindDirection() <= RMISSD + && sndata.get(kk).getWindSpeed() <= RMISSD) { + } else if (sndata.get(kk).getPressure() <= RMISSD) { + } else { + addDataToList(kk, sndata, outdat); + } + } + return outdat; + } + + /* + * Interpolate data to a single level, including surface. + */ + public List getSingLevel(float pres, + List sndata) { + NcSoundingLayer sl = new NcSoundingLayer(); + List sls = new ArrayList(); + sndata = removeUnderGround(sndata); + if (sndata.size() <= 1) + return missingSounding(); // Chin: check size again, after remove + // underground level, size changed + + for (int kk = 1; /* chin 0; */kk < sndata.size() - 1; kk++) { + if (pres > sndata.get(0).getPressure() || pres < 0.f) { + return missingSounding(); + } else { + + if (pres >= sndata.get(kk).getPressure()) { + float pt, pb, zt, zb, tt, tb, tdt, tdb, dt, db, st, sb; + pb = sndata.get(kk - 1).getPressure(); + pt = sndata.get(kk).getPressure(); + tb = sndata.get(kk - 1).getTemperature(); + tt = sndata.get(kk).getTemperature(); + tdb = sndata.get(kk - 1).getDewpoint(); + tdt = sndata.get(kk).getDewpoint(); + db = sndata.get(kk - 1).getWindDirection() % 360.f; + dt = sndata.get(kk).getWindDirection() % 360.f; + sb = sndata.get(kk - 1).getWindSpeed(); + st = sndata.get(kk).getWindSpeed(); + zb = sndata.get(kk - 1).getGeoHeight(); + zt = sndata.get(kk).getGeoHeight(); + sl.setPressure(pres); + + float rmult = (float) (Math.log(pres / pb) / Math.log(pt + / pb)); + sl.setTemperature(tb + (tt - tb) * rmult); + sl.setDewpoint(tdb + (tdt - tdb) * rmult); + if (Math.abs(db - dt) > 180.) { + if (db < dt) { + db = db + 360.f; + } else { + dt = dt + 360.f; + } + } + sl.setWindDirection(db + (dt - db) * rmult); + sl.setWindSpeed(sb + (st - sb) * rmult); + sl.setGeoHeight(zb + (zt - zb) * rmult); + sls.add(sl); + return sls; + } + } + } + return missingSounding(); + } + + /* + * Add data to output sounding profile. + */ + public void addDataToList(int index, List indat, + List outdat) { + NcSoundingLayer sl = new NcSoundingLayer(); + sl.setPressure(indat.get(index).getPressure()); + sl.setTemperature(indat.get(index).getTemperature()); + sl.setDewpoint(indat.get(index).getDewpoint()); + sl.setWindDirection(indat.get(index).getWindDirection()); + sl.setWindSpeed(indat.get(index).getWindSpeed()); + sl.setGeoHeight(indat.get(index).getGeoHeight()); + sl.setOmega(indat.get(index).getOmega()); + outdat.add(sl); + } + + /* + * Set missing to output sounding profile. + */ + public List missingSounding() { + List outdat = new ArrayList(); + NcSoundingLayer sl = new NcSoundingLayer(); + sl.setPressure(RMISSD); + sl.setTemperature(RMISSD); + sl.setDewpoint(RMISSD); + sl.setWindDirection(RMISSD); + sl.setWindSpeed(RMISSD); + sl.setGeoHeight(RMISSD); + sl.setOmega(RMISSD); + outdat.add(sl); + return outdat; + } + + /* + * Print the sounding data out. + */ + public void printOut(List sndata) { + for (NcSoundingLayer soundLy : sndata) { + System.out.print(" PRES: " + soundLy.getPressure() + " HGHT: " + + soundLy.getGeoHeight() + " TMPC: " + + soundLy.getTemperature() + " DWPC: " + + soundLy.getDewpoint() + " DRCT: " + + soundLy.getWindDirection() + " SPED: " + + soundLy.getWindSpeed() + " OMEG " + soundLy.getOmega() + + "\n"); + } + } + + /* + * Extrapolate missing height at the top level before sorting the data by + * height. + */ + private void fillMissingHeightAtTop(List sndata) { + int blev, kk; + float pb, zb, tb, tdb; + float pt, tt, tdt; + float znew; + boolean contin; + + int miss = 0; + contin = true; + if (sndata.get(sndata.size() - 1).getGeoHeight() != RMISSD) + return; + for (kk = sndata.size() - 1; kk > 0 && contin; kk--) { + if (sndata.get(kk).getGeoHeight() == RMISSD) { + miss++; + if (kk == 0) + return; + + if (sndata.get(kk - 1).getGeoHeight() != RMISSD) + contin = false; + } + } + + miss++; + if (miss > 0) { + blev = sndata.size() - miss; + pb = sndata.get(blev).getPressure(); + zb = sndata.get(blev).getGeoHeight(); + tb = sndata.get(blev).getTemperature(); + tdb = sndata.get(blev).getDewpoint(); + + for (kk = blev + 1; kk < sndata.size(); kk++) { + pt = sndata.get(kk).getPressure(); + tt = sndata.get(kk).getTemperature(); + tdt = sndata.get(kk).getDewpoint(); + float xxx = scaleHeight(tb, tt, tdb, tdt, pb, pt); + znew = moistHeight(zb, pb, pt, xxx); + if (znew == RMISSD) { + xxx = scaleHeight(tb, tb, tdb, tdb, pb, pt); + znew = moistHeight(zb, pb, pt, xxx); + } + + if (znew != RMISSD) { + sndata.get(kk).setGeoHeight(znew); + pb = pt; + tb = tt; + tdb = tdt; + zb = znew; + } + } + } + } + + /* + * Remove missing pressure from the sounding profile. + */ + public List removeMissingPressure( + List sndin) { + List sndout = new ArrayList(); + + for (int kk = 0; kk < sndin.size(); kk++) { + if (sndin.get(kk).getPressure() > 0.) { + addDataToList(kk, sndin, sndout); + } + } + return sndout; + } }