From 4d71c4d07db80638158d23cd2351afde71813756 Mon Sep 17 00:00:00 2001 From: Ron Anderson Date: Fri, 30 Jan 2015 09:25:55 -0600 Subject: [PATCH 01/34] Issue #4062 Fix AlertViz errors on some custom map scales. Change-Id: Id9facb0ba799c4a6272f57ffdc85e32699982b9c Former-commit-id: 80ecb8b27a7e442eedf0e776800d1cdf5398161e [formerly 4826b2a664ade311e29c36fb43a2fca71a795343] Former-commit-id: df90d1160d3731c0d6522589c9c22f3e7b4cacb3 --- .../uf/viz/core/maps/rsc/AbstractDbMapResource.java | 5 +++++ .../src/com/raytheon/viz/core/rsc/jts/JTSCompiler.java | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/cave/com.raytheon.uf.viz.core.maps/src/com/raytheon/uf/viz/core/maps/rsc/AbstractDbMapResource.java b/cave/com.raytheon.uf.viz.core.maps/src/com/raytheon/uf/viz/core/maps/rsc/AbstractDbMapResource.java index 04f938c1eb..3a43f70e8d 100644 --- a/cave/com.raytheon.uf.viz.core.maps/src/com/raytheon/uf/viz/core/maps/rsc/AbstractDbMapResource.java +++ b/cave/com.raytheon.uf.viz.core.maps/src/com/raytheon/uf/viz/core/maps/rsc/AbstractDbMapResource.java @@ -58,6 +58,7 @@ import com.vividsolutions.jts.geom.impl.PackedCoordinateSequenceFactory; * ------------ ---------- ----------- -------------------------- * Aug 10, 2011 randerso Initial creation * Apr 17, 2014 #2997 randerso Moved buildBoundingGeometry up from DbMapResource + * Jan 29, 2015 #4062 randerso Added a buffer to bounding Geometry * * * @@ -272,6 +273,10 @@ public abstract class AbstractDbMapResource * @@ -476,6 +478,8 @@ public class JTSCompiler { corrector.correct(geom.asLatLon())); } catch (FactoryException e) { throw new VizException("Error creating transform to Lat/Lon", e); + } catch (ProjectionException e) { + // ignore this exception so it doesn't cause pop ups } catch (Exception e) { throw new VizException( "Error transforming geometry into Lat/Lon", e); @@ -487,6 +491,8 @@ public class JTSCompiler { } catch (FactoryException e) { throw new VizException( "Error creating transform to descriptor pixel space", e); + } catch (ProjectionException e) { + // ignore this exception so it doesn't cause pop ups } catch (Exception e) { throw new VizException( "Error transforming geometry into descriptor pixel space", From c2798efc95277ea02de3379aa37c1f96b5472c2e Mon Sep 17 00:00:00 2001 From: "Ying-Lian.Shi" Date: Fri, 30 Jan 2015 23:01:23 +0000 Subject: [PATCH 02/34] ASM #17081 Service Backup - add vtec Change-Id: Id2e4d8024699c3a95c1c945919fc757b72d3d1aa Former-commit-id: 55674f46fa12b7ff881f54a12c57ef2e2971ed70 [formerly 82e8c3ce886e0be2cf1044c87ba0e2cf004c1afe] Former-commit-id: bf2d61894fe78483d0069f8b3c6e7c3a94e899c4 --- .../svcBackup/ServiceBackup/scripts/proc_receive_config | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/edexOsgi/com.raytheon.uf.tools.gfesuite.servicebackup/svcBackup/ServiceBackup/scripts/proc_receive_config b/edexOsgi/com.raytheon.uf.tools.gfesuite.servicebackup/svcBackup/ServiceBackup/scripts/proc_receive_config index fb844a3937..6746fb5786 100644 --- a/edexOsgi/com.raytheon.uf.tools.gfesuite.servicebackup/svcBackup/ServiceBackup/scripts/proc_receive_config +++ b/edexOsgi/com.raytheon.uf.tools.gfesuite.servicebackup/svcBackup/ServiceBackup/scripts/proc_receive_config @@ -27,6 +27,7 @@ # ------------ ---------- ----------- -------------------------- # 03/20/14 #2933 randerso Changed PRDDIR and LOGDIR to use # Backup site's configuration +# 01/30/15 #17081 lshi Added backup vtec ############################################################################## import_file=${1} @@ -175,6 +176,9 @@ log_msg 80 cp -r GFEconfig/edex_static/site/smartinit ${edex_site_si_dest} log_msg 90 cp -r GFEconfig/cave_static/site/* ${cave_site_dest} +# DR 17081 +log_msg 91 +cp -r GFEconfig/common_static/site/vtec ${common_site_dest} log_msg 93 # DR 16464 cp -a GFEConfig/site/rsync_parms.${SITE_CAPS} ${rsync_parms_dest}.${SITE_CAPS} From e55804c0b4798c6a4a9e9d105be2b2363cffb991 Mon Sep 17 00:00:00 2001 From: Mike Duff Date: Mon, 2 Feb 2015 10:16:45 -0600 Subject: [PATCH 03/34] Issue #4030 - Added functionality to convert NDM menu files to A2 menu files and have them droped into the NDM endpoint. Change-Id: Ieb87acafed0d9e756be49c3111ba97a921ca425e Former-commit-id: 8f2350f6604e7b3c567fb264738c6678466a4852 [formerly 3f5edb033f8b54908adcd3a751eabe189570d338] Former-commit-id: 71cc843faac323e13c7805e0075838cc7b955d22 --- .../build.edex/esb/bin/ndmMenuIngester.sh | 71 ++++ .../META-INF/MANIFEST.MF | 3 +- .../redbook/ingest/RedbookMenuSubscriber.java | 13 +- .../plugin/redbook/ingest/xml/MenuEntry.java | 15 +- .../redbook/ingest/xml/RedbookMenusXML.java | 5 + .../plugin/redbook/menu/NdmMenuConverter.java | 360 ++++++++++++++++++ 6 files changed, 459 insertions(+), 8 deletions(-) create mode 100644 edexOsgi/build.edex/esb/bin/ndmMenuIngester.sh create mode 100644 edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java diff --git a/edexOsgi/build.edex/esb/bin/ndmMenuIngester.sh b/edexOsgi/build.edex/esb/bin/ndmMenuIngester.sh new file mode 100644 index 0000000000..7ce8a1028d --- /dev/null +++ b/edexOsgi/build.edex/esb/bin/ndmMenuIngester.sh @@ -0,0 +1,71 @@ +#!/bin/bash +## +# This software was developed and / or modified by Raytheon Company, +# pursuant to Contract DG133W-05-CQ-1067 with the US Government. +# +# U.S. EXPORT CONTROLLED TECHNICAL DATA +# This software product contains export-restricted data whose +# export/transfer/disclosure is restricted by U.S. law. Dissemination +# to non-U.S. persons whether in the United States or abroad requires +# an export license or other authorization. +# +# Contractor Name: Raytheon Company +# Contractor Address: 6825 Pine Street, Suite 340 +# Mail Stop B8 +# Omaha, NE 68106 +# 402.291.0100 +# +# See the AWIPS II Master Rights File ("Master Rights File.pdf") for +# further licensing information. +## +# NDM Menu File Ingester + +if [ "$1" == "-help" ]; +then + echo "" + echo "ndmMenuIngester.sh " + echo " ndmFileDirectory is the directory holding the ndm files" + echo " ndmMenuFileName is the name of the ndm menu file to convert" + echo "" + echo " Required files: redbookDataKeys.txt, redbookDepictKeys.txt," + echo " redbookProductButtons.txt, and the menu file to convert" + exit; +fi +if [ ! -d "$1" ]; +then + echo "Directory [$1] does not exist!" + exit +fi + +if [ ! -f "$1/$2" ]; +then + echo "File [$2] does not exist!" + exit +fi +SRC_DIR=$1 +MENU_FILE=$2 +path_to_script=`readlink -f $0` +dir=$(dirname $path_to_script) +dir=$(dirname $dir) +AWIPS_HOME=$(dirname $dir) + +# Search for jars +EDEX_PLUGINS="$AWIPS_HOME/edex/lib/" +JARS=$(find $EDEX_PLUGINS -name "*.jar") + +# Add jars to classpath +addSep=false +for i in $JARS +do + if [[ "$addSep" == true ]]; + then + LOCAL_CLASSPATH=$LOCAL_CLASSPATH":"$i + else + LOCAL_CLASSPATH=$i + addSep=true + fi +done + +JAVA_INSTALL=/awips2/java/ +${JAVA_INSTALL}/bin/java -classpath $LOCAL_CLASSPATH com.raytheon.uf.edex.plugin.redbook.menu.NdmMenuConverter $SRC_DIR $MENU_FILE + diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/META-INF/MANIFEST.MF b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/META-INF/MANIFEST.MF index 1dbdd18505..8cfd6c28cb 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/META-INF/MANIFEST.MF +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/META-INF/MANIFEST.MF @@ -12,7 +12,8 @@ Require-Bundle: com.raytheon.edex.common, com.raytheon.uf.common.status;bundle-version="1.12.1174", com.raytheon.uf.edex.ndm;bundle-version="1.14.0", com.raytheon.uf.edex.menus;bundle-version="1.0.0", - com.raytheon.uf.common.dataplugin.redbook;bundle-version="1.14.0" + com.raytheon.uf.common.dataplugin.redbook;bundle-version="1.14.0", + org.apache.commons.lang;bundle-version="2.3.0" Export-Package: com.raytheon.uf.edex.plugin.redbook, com.raytheon.uf.edex.plugin.redbook.decoder Bundle-RequiredExecutionEnvironment: JavaSE-1.6 diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/RedbookMenuSubscriber.java b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/RedbookMenuSubscriber.java index 2c6e47864c..bf1fa4a877 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/RedbookMenuSubscriber.java +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/RedbookMenuSubscriber.java @@ -47,6 +47,7 @@ import com.raytheon.uf.edex.plugin.redbook.menu.RedbookUaMenuUtil; * Mar 19, 2014 2857 mpduff Implement NCO. * Mar 19, 2014 2859 mpduff Implement MPC. * Mar 19, 2014 2860 mpduff Implement Upper Air. + * Jan 28, 2015 4030 mpduff Changed constants to public. * * * @@ -60,22 +61,22 @@ public class RedbookMenuSubscriber implements INationalDatasetSubscriber { .getHandler(RedbookMenuSubscriber.class); /** Hazard menu file */ - private static final String HAZARD_MENU_FILE = "RedbookHazardMenus.xml"; + public static final String HAZARD_MENU_FILE = "RedbookHazardMenus.xml"; /** HPC menu file */ - private static final String HPC_MENU_FILE = "RedbookHPCMenus.xml"; + public static final String HPC_MENU_FILE = "RedbookHPCMenus.xml"; /** CPC menu file */ - private static final String CPC_MENU_FILE = "RedbookCPCMenus.xml"; + public static final String CPC_MENU_FILE = "RedbookCPCMenus.xml"; /** MPC menu file */ - private static final String MPC_MENU_FILE = "RedbookMPCMenus.xml"; + public static final String MPC_MENU_FILE = "RedbookMPCMenus.xml"; /** NCO menu file */ - private static final String NCO_MENU_FILE = "RedbookNCOMenus.xml"; + public static final String NCO_MENU_FILE = "RedbookNCOMenus.xml"; /** Upper air menu file */ - private static final String UA_MENU_FILE = "RedbookUAMenus.xml"; + public static final String UA_MENU_FILE = "RedbookUAMenus.xml"; /** * {@inheritDoc} diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/MenuEntry.java b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/MenuEntry.java index 7a08a9b1ce..6ff7ae392a 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/MenuEntry.java +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/MenuEntry.java @@ -39,6 +39,7 @@ import javax.xml.bind.annotation.XmlElements; * ------------ ---------- ----------- -------------------------- * Mar 12, 2014 2855 mpduff Initial creation * Mar 19, 2014 2860 mpduff Added DataUri. + * Jan 28, 2015 4030 mpduff Added addMenuEntry method. * * * @@ -208,7 +209,7 @@ public class MenuEntry { * @param menuEntryList * the menuEntryList to set */ - public void setMenuEntryyList(List menuEntryList) { + public void setMenuEntryList(List menuEntryList) { this.menuEntryList = menuEntryList; } @@ -227,6 +228,18 @@ public class MenuEntry { this.dataUri = dataUri; } + /** + * Add a {@link MenuEntry} + * + * @param entry + */ + public void addMenuEntry(MenuEntry entry) { + if (this.menuEntryList == null) { + menuEntryList = new ArrayList(); + } + menuEntryList.add(entry); + } + /* * (non-Javadoc) * diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/RedbookMenusXML.java b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/RedbookMenusXML.java index 1aeba28642..1079d5cc33 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/RedbookMenusXML.java +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/ingest/xml/RedbookMenusXML.java @@ -39,6 +39,7 @@ import javax.xml.bind.annotation.XmlRootElement; * ------------ ---------- ----------- -------------------------- * Mar 07, 2014 2858 mpduff Initial creation. * Mar 17, 2014 2855 mpduff Renamed to RedbookMenusXML.java. + * Jan 28, 2015 4030 mpduff Added addMenuEntry method. * * * @@ -78,6 +79,10 @@ public class RedbookMenusXML { this.menuEntryList = menuEntryList; } + public void addMenuEntry(MenuEntry entry) { + menuEntryList.add(entry); + } + /* * (non-Javadoc) * diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java new file mode 100644 index 0000000000..2420560859 --- /dev/null +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java @@ -0,0 +1,360 @@ +/** + * This software was developed and / or modified by Raytheon Company, + * pursuant to Contract DG133W-05-CQ-1067 with the US Government. + * + * U.S. EXPORT CONTROLLED TECHNICAL DATA + * This software product contains export-restricted data whose + * export/transfer/disclosure is restricted by U.S. law. Dissemination + * to non-U.S. persons whether in the United States or abroad requires + * an export license or other authorization. + * + * Contractor Name: Raytheon Company + * Contractor Address: 6825 Pine Street, Suite 340 + * Mail Stop B8 + * Omaha, NE 68106 + * 402.291.0100 + * + * See the AWIPS II Master Rights File ("Master Rights File.pdf") for + * further licensing information. + **/ +package com.raytheon.uf.edex.plugin.redbook.menu; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.Marshaller; + +import org.apache.commons.lang.StringUtils; + +import com.raytheon.uf.edex.plugin.redbook.ingest.RedbookMenuSubscriber; +import com.raytheon.uf.edex.plugin.redbook.ingest.xml.MenuEntry; +import com.raytheon.uf.edex.plugin.redbook.ingest.xml.MenuEntryType; +import com.raytheon.uf.edex.plugin.redbook.ingest.xml.RedbookMenusXML; + +/** + * This class is called from /awips2/edex/bin/ndmMenuIngester.sh. + * + * It reads in the NDM menu files and outputs an A2 version into the NDM + * endpoint on edex. + * + *
+ * 
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    Engineer    Description
+ * ------------ ---------- ----------- --------------------------
+ * Jan 30, 2015   4030     mpduff      Initial creation
+ * 
+ * 
+ * + * @author mpduff + * @version 1.0 + */ + +public class NdmMenuConverter { + private static final String WMO = "wmo"; + + private static final String OUTPUT_PATH = File.separator + "awips2" + + File.separator + "edex" + File.separator + "data" + + File.separator + "ndm" + File.separator; + + private static final String HPC_FILE = "redbookHPCMenus.txt"; + + private static final String CPC_FILE = "redbookCPCMenus.txt"; + + private static final String NCO_FILE = "redbookNCOMenus.txt"; + + private static final String HAZARDS_FILE = "redbookHazardMenus.txt"; + + private static final String MARINE_FILE = "redbookMarineMenus.txt"; + + private static final String UPPER_AIR_FILE = "redbookUpperAirMenus.txt"; + + private String dataKeysPath; + + private String depictKeysPath; + + private String menuFilePath; + + private String productButtonPath; + + private JAXBContext jax; + + private Marshaller marshaller; + + private File depictFile; + + public NdmMenuConverter() { + createContext(); + } + + public void convert() { + RedbookMenusXML menuXml = new RedbookMenusXML(); + MenuEntry titleMenuEntry; + int sepCounter = 0; + List subMenuList = new ArrayList(); + + try { + File dataFile = new File(this.dataKeysPath); + File menuFile = new File(this.menuFilePath); + depictFile = new File(this.depictKeysPath); + File productButtonFile = new File(this.productButtonPath); + + List dataKeys = Files.readAllLines(dataFile.toPath(), + Charset.defaultCharset()); + List depictKeys = Files.readAllLines(depictFile.toPath(), + Charset.defaultCharset()); + List lines = Files.readAllLines(menuFile.toPath(), + Charset.defaultCharset()); + List productButtonKeys = Files.readAllLines( + productButtonFile.toPath(), Charset.defaultCharset()); + Map menuTextMap = getMenuTextMap(productButtonKeys); + Map dataKeyMap = getSubstitutionMap(dataKeys); + + MenuEntry subMenuEntry = null; + + int subMenuCount = -1; + for (String line : lines) { + line = line.trim(); + if (line.startsWith("submenu")) { + subMenuCount++; + if (line.contains("&")) { + line = line.replace("&", "&&"); + } + + String[] parts = line.split(":"); + String text = parts[1].replace("\"", ""); + + subMenuEntry = new MenuEntry(); + subMenuEntry.setFile(null); + subMenuEntry.setType(MenuEntryType.Submenu); + subMenuEntry.setText(text.trim()); + + subMenuList.add(subMenuEntry); + } else if (line.startsWith("title")) { + String[] parts = line.split(":"); + String text = parts[1].replace("\"", ""); + titleMenuEntry = new MenuEntry(); + titleMenuEntry.setFile(null); + titleMenuEntry.setType(MenuEntryType.Title); + titleMenuEntry.setText(text); + titleMenuEntry.setId(text); + subMenuList.get(subMenuCount).addMenuEntry(titleMenuEntry); + } else if (line.startsWith("productButton")) { + String[] parts = line.split(":"); + MenuEntry me = new MenuEntry(); + me.setFile(null); + me.setType(MenuEntryType.ProductButton); + /* + * There are certain productButtons in the NCO menu data + * keys in the (25000 range) that have data keys that don't + * map to anything. This results in those menu items not + * being created. The site will need to fix this after + * generating the new menus. + */ + String dataKey = parts[1].trim().substring(0, 4); + StringBuilder subValue = new StringBuilder(); + // Find the matching value + for (String depictKeyLine : depictKeys) { + if (depictKeyLine.trim().startsWith(dataKey)) { + String[] depictKeyParts = depictKeyLine + .split("\\|"); + me.setText(menuTextMap.get(dataKey)); + me.setId(depictKeyParts[6].trim()); + subMenuList.get(subMenuCount).addMenuEntry(me); + + String[] subParts = depictKeyParts[2].split(","); + MenuEntry subEntry = new MenuEntry(); + subEntry.setFile(null); + subEntry.setType(MenuEntryType.Substitute); + subEntry.setKey(WMO); + + for (String subPart : subParts) { + for (String key : dataKeyMap.keySet()) { + if (key.startsWith(subPart)) { + subValue.append(dataKeyMap.get(key)) + .append(","); + break; + } + } + } + + String subValueStr = subValue.toString(); + subValueStr = StringUtils.removeEnd(subValueStr, + ","); + subEntry.setValue(subValueStr); + me.addMenuEntry(subEntry); + break; + } + } + } else if (line.startsWith("endSubmenu")) { + // subMenuList.add(subMenuEntry); + MenuEntry closedSubMenu = subMenuList.remove(subMenuCount); + subMenuCount--; + if (subMenuCount == -1) { + menuXml.addMenuEntry(closedSubMenu); + } else { + subMenuList.get(subMenuCount).addMenuEntry( + closedSubMenu); + } + } else if (line.startsWith("separator")) { + MenuEntry sep = new MenuEntry(); + sep.setFile(null); + sep.setType(MenuEntryType.Separator); + sep.setId("Separator" + sepCounter++); + subMenuList.get(subMenuCount).addMenuEntry(sep); + } + } + + // Set output file name + String inputFileName = menuFile.getName(); + String outputFileName = null; + if (inputFileName.equals(CPC_FILE)) { + outputFileName = RedbookMenuSubscriber.CPC_MENU_FILE; + } else if (inputFileName.equals(HPC_FILE)) { + outputFileName = RedbookMenuSubscriber.HPC_MENU_FILE; + } else if (inputFileName.equals(NCO_FILE)) { + outputFileName = RedbookMenuSubscriber.NCO_MENU_FILE; + } else if (inputFileName.equals(HAZARDS_FILE)) { + outputFileName = RedbookMenuSubscriber.HAZARD_MENU_FILE; + } else if (inputFileName.equals(MARINE_FILE)) { + outputFileName = RedbookMenuSubscriber.MPC_MENU_FILE; + } else if (inputFileName.equals(UPPER_AIR_FILE)) { + outputFileName = RedbookMenuSubscriber.UA_MENU_FILE; + } else { + throw new IOException("Error processing file"); + } + + marshaller.marshal(menuXml, new File(OUTPUT_PATH + outputFileName)); + } catch (Exception e) { + System.err.println("Error occurred processing file: " + + menuFilePath); + e.printStackTrace(); + } + } + + /** + * Get a map of menu keys to menu text. + * + * @param productButtonKeys + * List of strings from redbookProductButtons.txt + * + * @return Map for key -> menu text + */ + private Map getMenuTextMap(List productButtonKeys) { + Map menuTextMap = new HashMap(); + for (String line : productButtonKeys) { + line = line.trim(); + // Skip comment lines + if (line.startsWith("#")) { + continue; + } + + String[] parts = line.split("\\|"); + menuTextMap.put(parts[0].trim(), parts[2].trim()); + } + + return menuTextMap; + } + + /** + * Get a map of key to substitution values. + * + * @param dataKeys + * List of strings from the redbookDataKeys.txt file + * @returnMap for key -> substitution string + */ + private Map getSubstitutionMap(List dataKeys) { + Map dataKeyMap = new HashMap(); + for (String line : dataKeys) { + line = line.trim(); + // Skip comment lines + if (line.startsWith("#")) { + continue; + } + String[] parts = line.split("\\|"); + dataKeyMap.put(parts[0].trim(), parts[10].substring(0, 6)); + } + + return dataKeyMap; + } + + public String getDataKeysPath() { + return dataKeysPath; + } + + public void setDataKeysPath(String dataKeysPath) { + this.dataKeysPath = dataKeysPath; + } + + public String getDepictKeysPath() { + return depictKeysPath; + } + + public void setDepictKeysPath(String depictKeysPath) { + this.depictKeysPath = depictKeysPath; + } + + public String getMenuFilePath() { + return menuFilePath; + } + + public void setMenuFilePath(String menuFilePath) { + this.menuFilePath = menuFilePath; + } + + public void setProductButtonPath(String productButtonPath) { + this.productButtonPath = productButtonPath; + } + + public String getProductButtonPath() { + return this.productButtonPath; + } + + private void createContext() { + Class[] classes = new Class[] { MenuEntry.class, MenuEntryType.class, + RedbookMenusXML.class }; + + try { + jax = JAXBContext.newInstance(classes); + this.marshaller = jax.createMarshaller(); + this.marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public static void main(String[] args) { + + if (args.length != 2) { + System.err.println("Unexpected Arguments"); + System.err + .println("Expecting local NDM directory and NDM File Menu Name"); + return; + } + + String dirPath = args[0]; + if (!dirPath.endsWith(File.separator)) { + dirPath = dirPath.concat(File.separator); + } + String menuFile = dirPath + args[1]; + String dataKeysFile = dirPath + "redbookDataKeys.txt"; + String depictKeysFile = dirPath + "redbookDepictKeys.txt"; + String productButtonFile = dirPath + "redbookProductButtons.txt"; + + NdmMenuConverter converter = new NdmMenuConverter(); + converter.setDataKeysPath(dataKeysFile); + converter.setMenuFilePath(menuFile); + converter.setDepictKeysPath(depictKeysFile); + converter.setProductButtonPath(productButtonFile); + converter.convert(); + + } +} From 7be8540aecf0e8e85a211eb6a0f9dee07b5ec7c4 Mon Sep 17 00:00:00 2001 From: Roger Ferrel Date: Mon, 2 Feb 2015 16:20:27 -0600 Subject: [PATCH 04/34] Issue #4066 Added Earth Networks Total Lightning to pqact.template and RAW_DATA.xml Change-Id: I09ccfea7e02134339771550fb915bb3516067320 Former-commit-id: 6e4b93ed155b6817a3d8b6f2af9beef87a521ba0 [formerly 4d9df40733b0ac8cfa40974dd13abe2e204ab3bd] Former-commit-id: ba9e38efb2b69fe66f70e3ad721349e789ba895d --- .../utility/common_static/base/archiver/purger/RAW_DATA.xml | 3 ++- .../awips2.core/Installer.ldm/patch/etc/pqact.conf.template | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/edexOsgi/com.raytheon.uf.edex.archive/utility/common_static/base/archiver/purger/RAW_DATA.xml b/edexOsgi/com.raytheon.uf.edex.archive/utility/common_static/base/archiver/purger/RAW_DATA.xml index c5a7c71224..b1622f34b7 100644 --- a/edexOsgi/com.raytheon.uf.edex.archive/utility/common_static/base/archiver/purger/RAW_DATA.xml +++ b/edexOsgi/com.raytheon.uf.edex.archive/utility/common_static/base/archiver/purger/RAW_DATA.xml @@ -29,6 +29,7 @@ * Dec 12, 2013 2624 rferrel Document Julian time stamp. * May 14, 2014 2881 rferrel Change retention times and data set modifications. * Aug 25, 2014 3537 rferrel Fixed dirPattern in Category Local. + * Feb 02, 2015 4066 rferrel Added lightning to Observation category. * * @author rferrel * @version 1.0 @@ -208,7 +209,7 @@ Observation 168 - (airep|binlightning|bufrascat|bufrhdw|bufrmthdw|bufrssmi|lsr|MAROB|maritime|metar|misc_sfc_obs|pirep|sfcobs|shef|svrwx|synoptic)/(\d{4})(\d{2})(\d{2})/(\d{2}) + (airep|binlightning|bufrascat|bufrhdw|bufrmthdw|bufrssmi|entlightning|lsr|MAROB|maritime|metar|misc_sfc_obs|pirep|sfcobs|shef|svrwx|synoptic)/(\d{4})(\d{2})(\d{2})/(\d{2}) {1} 2,3,4,5 diff --git a/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template b/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template index 327be8ba02..2ccaf5a920 100644 --- a/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template +++ b/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template @@ -36,6 +36,7 @@ #20130624 1966 rferrel The acars pattern changed to place files in the proper subdirectories. # Name changes to reflect plugin names for modelsounding, goessounding, poessounding. #20140424 3068 dgilling Add pattern for MetOp-B ASCAT T winds. +#20150202 4066 rferrel Add pattern form Earth Networks Total Lightning: SFPA42 #*************************************************************** # AWIPS 1 PATTERN GRAPHIC ^[PQ].* /redbook/Raw # PGNA00 KWNS 010001 !redbook 1_1/NMCGPHMCD/MCDSUM/PXSF001CN/20110201 0001 @@ -384,6 +385,11 @@ HDS ^(IUS(Z[0-9]|Y4)[0-9]) ([A-Z]{4}) (..)(..)(..) HDS ^(SF(US|PA)41) ([A-Z]{4}) (..)(..)(..) FILE -overwrite -log -edex -close /data_store/binlightning/(\4:yyyy)(\4:mm)\4/\5/\1_\3_\4\5\6_(seq).nldn.%Y%m%d%H +# WMO Heading for Earth Networks Total Lightning + +HDS ^(SF(US|PA)42) ([A-Z]{4}) (..)(..)(..) + FILE -overwrite -log -edex -close /data_store/entlightning/(\4:yyyy)(\4:mm)\4/\5/\1_\3_\4\5\6_(seq).nldn.%Y%m%d%H + # AWIPS1: TEXT ^[ABCFMNRSUVW]......[KPTMC] /text/NO_STORE # TEXT ^[ABCFMNRSUVW].....[KPTMC] /text/NO_STORE # TEXT ^DF.* /text/NO_STORE From c2a3541dd84d20bc5042c27cd413e490d8a7ca8a Mon Sep 17 00:00:00 2001 From: "steve.naples" Date: Tue, 3 Feb 2015 19:32:13 +0000 Subject: [PATCH 05/34] ASM #16993 MPE: Fixed issue with Climo and data units issue for Monthly Precip. Change-Id: I958266658373d62ec8d61998cebed3099f3a11cf Former-commit-id: eccd38608c64e164e1b9d7d29b1c0921fc83d65b [formerly 236ebde830681a338e32b938c5fc709fae582e42] Former-commit-id: 82b0619ddd02cada70807be063a5cffcef9ef2fc --- .../src/com/raytheon/viz/mpe/util/InitPrecipClimo.java | 5 +++-- .../src/com/raytheon/viz/mpe/util/MeanMonthlyPrecip.java | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/InitPrecipClimo.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/InitPrecipClimo.java index ecc9a5a173..0d52078b4b 100644 --- a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/InitPrecipClimo.java +++ b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/InitPrecipClimo.java @@ -36,6 +36,7 @@ import com.raytheon.viz.mpe.util.DailyQcUtils.Station; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Feb 23, 2009 snaples Initial creation + * Feb 3, 2015 16993 snaples Fixed if condition on cparm. * * * @@ -133,8 +134,8 @@ public class InitPrecipClimo { continue; } - if (tokens[1].equalsIgnoreCase("PPMPBCM") - && tokens[1].equalsIgnoreCase("PPMRZCM")) { + if (!(tokens[1].equalsIgnoreCase("PPMPBCM")) + && !(tokens[1].equalsIgnoreCase("PPMRZCM"))) { line = in.readLine(); ++record_count; continue; diff --git a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/MeanMonthlyPrecip.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/MeanMonthlyPrecip.java index e204132de6..206b6049a9 100644 --- a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/MeanMonthlyPrecip.java +++ b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/MeanMonthlyPrecip.java @@ -44,6 +44,7 @@ import com.raytheon.viz.mpe.core.MPEDataManager; * ------------ ---------- ----------- -------------------------- * Feb 24, 2009 snaples Initial creation * April , 2012 8672 lbousaidi fixed the reading of the PRISM data. + * Feb 3, 2015 16993 snaples fixed color scale data conversion issue. * * * @author snaples @@ -90,9 +91,8 @@ public class MeanMonthlyPrecip { displayUnit = NonSI.INCH; dataUnit = SI.MILLIMETER; cmc.setDisplayUnit(displayUnit); - cmc.setColorMapUnit(SI.MILLIMETER); cmc.setDataUnit(dataUnit); - UnitConverter dataToImage = cmc.getColorMapToDisplayConverter();//cmc.getDataToImageConverter(); + UnitConverter dataToImage = cmc.getDataToImageConverter(); /* * Loop over the months. Determine for which months PRISM data are From a8f7710a659a49d3eb0a690cd1eb1ba73509acfc Mon Sep 17 00:00:00 2001 From: "steve.naples" Date: Tue, 3 Feb 2015 21:12:21 +0000 Subject: [PATCH 06/34] ASM #15059 MPE: Updated Save Best Estimate to use lower case in query of mapx field. Change-Id: I303064502e2c7eea80577ab937f702c99b2e2065 Former-commit-id: fb6a0c3c39ae403dffdf9739baa127d4f1ecde42 [formerly d967cdf333a9c71d4f98402a123e01359dca3bc3] Former-commit-id: e82d4d40e3c83013d975d56c039091605ec9fa2c --- .../src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java index 4b94128770..051d55a573 100644 --- a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java +++ b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java @@ -337,7 +337,7 @@ public class SaveBestEstimate { Rwresult pRWResultNode = pRWResultHead.get(0); /* Update the elements in the RWResult node. */ - pRWResultNode.setMapxFieldType(fldtype); + pRWResultNode.setMapxFieldType(fldtype.toLowerCase()); pRWResultNode.setAutoSave(asave); pRWResultNode.setDrawPrecip(drpr); pRWResultNode.setLastSaveTime(SimulatedTime.getSystemTime() From bf4f6be9c09386b9e0fb3be72e748e47205bb7f2 Mon Sep 17 00:00:00 2001 From: Chip Gobs Date: Wed, 4 Feb 2015 14:15:14 -0500 Subject: [PATCH 07/34] VLab Issue #6312 - RM 17094 - MPE - mapx_field_type too long for RWResult table; fixes #6312 Change-Id: I320c052a7d7a1579a668935e848aa378874c3ad5 Former-commit-id: 8e8e6cf1ce5cdb903ac608058d2847125da3dddb [formerly 9f66b0830de2377da2dc3e5598d264afc631b517] Former-commit-id: 2da6f9022d5f04b2eb0b81034b3c11006f59412e --- .../viz/mpe/ui/actions/SaveBestEstimate.java | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java index 4b94128770..24d650f837 100644 --- a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java +++ b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/SaveBestEstimate.java @@ -23,6 +23,7 @@ * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Jan 7, 2015 16954 cgobs Fix for cv_use issue - using getFieldName() in certain parts. + * Feb 4, 2015 17094 cgobs Fix for fieldType being too long for mapx_field_type column in RWResult table. * **/ package com.raytheon.viz.mpe.ui.actions; @@ -33,8 +34,10 @@ import java.io.IOException; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Date; +import java.util.HashMap; import java.util.Iterator; import java.util.List; +import java.util.Map; import javax.imageio.ImageIO; import javax.imageio.ImageWriter; @@ -337,6 +340,9 @@ public class SaveBestEstimate { Rwresult pRWResultNode = pRWResultHead.get(0); /* Update the elements in the RWResult node. */ + + fldtype = checkAndModifyMapxFieldType(fldtype); + pRWResultNode.setMapxFieldType(fldtype); pRWResultNode.setAutoSave(asave); pRWResultNode.setDrawPrecip(drpr); @@ -359,4 +365,32 @@ public class SaveBestEstimate { } } + + private static String checkAndModifyMapxFieldType(String fieldType) { + + // This method changes fieldType to lowercase. + // It also shortens fieldTypes as needed to fit into the mapx_field_type column in the RWResult table. + // Note: the mapx_field_type column is informational only. It is not used by the code + // other than reading and writing from and to the database. + + String newFieldType = null; + String lowerCaseFieldType = fieldType.toLowerCase(); + + final Map conversionTable = new HashMap(); + + conversionTable.put("localfield1", "localfld1"); + conversionTable.put("localfield2", "localfld2"); + conversionTable.put("localfield3", "localfld3"); + + conversionTable.put("avgrdmosaic", "avgrdmos"); + conversionTable.put("maxrdmosaic", "maxrdmos"); + + + newFieldType = conversionTable.get(lowerCaseFieldType); + if (newFieldType == null) + { + newFieldType = lowerCaseFieldType; + } + return newFieldType; + } } From cdf7c63ce1d48c77f16c4fafd274b17e89ee8b69 Mon Sep 17 00:00:00 2001 From: Chip Gobs Date: Wed, 4 Feb 2015 14:27:26 -0500 Subject: [PATCH 08/34] VLab Issue #6313 - RM 17069 - MPE mpe_generate_list token length too short; fixes #6313 Change-Id: I198f6016f10cdaf6c105c89bd7a4c9e445b9a98b Former-commit-id: ca7afdcf6cd76366f3c80e28e0cca634c663aee2 [formerly 919269c680602c639de54811dd1a65d4aa67fdad] Former-commit-id: c60c8ed57174b87eaa403725319e5a18576db21b --- .../src/MPEUtil/TEXT/get_mpe_product_state.c | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/nativeLib/rary.ohd.pproc/src/MPEUtil/TEXT/get_mpe_product_state.c b/nativeLib/rary.ohd.pproc/src/MPEUtil/TEXT/get_mpe_product_state.c index bb67230ab0..2e82710bcd 100644 --- a/nativeLib/rary.ohd.pproc/src/MPEUtil/TEXT/get_mpe_product_state.c +++ b/nativeLib/rary.ohd.pproc/src/MPEUtil/TEXT/get_mpe_product_state.c @@ -30,6 +30,7 @@ * new local fields to * mpe_qpe_fields and * mpe_qpe_dependencies +* 2/4/2015 C Gobs DR 17069 - mpe_generate_list - token value too short ******************************************************************************** */ #include @@ -130,6 +131,7 @@ const static char * mpe_qpe_dependencies [ NUM_BEST_PRODUCTS ] = /* GageOnly Generation rule in case mpe_del_gage_zeros token is OFF. */ const static char * gageonly_del_gages_off = "" ; +#define MPE_GENERATE_LIST_REPLY_LEN 512 /******************************************************************************* * MODULE NUMBER: 1 @@ -514,7 +516,7 @@ void get_mpe_product_state ( const char * product , const int * product_len , char mpe_del_gage_zeros_reply [ MPE_PRODUCT_REPLY_LEN ] = { '\0' } ; char mpe_generate_areal_qpe_reply [ MPE_PRODUCT_REPLY_LEN ] = {'\0'}; static char * mpe_generate_list_token = MPE_GENERATE_LIST_TOKEN ; - char mpe_generate_list_reply [ MPE_PRODUCT_REPLY_LEN ] = { '\0' } ; + char mpe_generate_list_reply [ MPE_GENERATE_LIST_REPLY_LEN ] = { '\0' } ; static char mpe_qpe_fieldtype [ BESTFIELD_LEN] = { '\0' } ; char * pChar = NULL ; char * pString = NULL ; @@ -598,9 +600,10 @@ void get_mpe_product_state ( const char * product , const int * product_len , "empty.\n" , mpe_generate_list_token ) ; } - memset ( mpe_generate_list_reply , '\0' , MPE_PRODUCT_REPLY_LEN ) ; + memset ( mpe_generate_list_reply , '\0' , MPE_GENERATE_LIST_REPLY_LEN ) ; } - printf("get_mpe_product_state(): mpe_generate_list_reply = :%s: \n", mpe_generate_list_reply); + printf("get_mpe_product_state(): mpe_generate_list_reply = :%s: characters in mpe_generate_list_reply = %d max = %d\n", mpe_generate_list_reply, + strlen(mpe_generate_list_reply), MPE_GENERATE_LIST_REPLY_LEN); /* Get the value of the mpe_del_gage_zeros token. */ request_len = strlen ( mpe_del_gage_zeros_token ) ; From 9e6dbcb03d8080aa7b1b155628c9f947ab43fee1 Mon Sep 17 00:00:00 2001 From: Mike Duff Date: Wed, 4 Feb 2015 16:59:08 -0600 Subject: [PATCH 09/34] Issue #4030 - Added check for blank lines Former-commit-id: b9557d58b88ff0720ad1fab4a525eb4329dd39f0 [formerly 7252df4112903132ae0c1bbe36bdc66add219ae5] Former-commit-id: bd16f5b1d17f7de52d4477864f74ff7de68878c7 --- .../uf/edex/plugin/redbook/menu/NdmMenuConverter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java index 2420560859..607b9da1c4 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java +++ b/edexOsgi/com.raytheon.uf.edex.plugin.redbook/src/com/raytheon/uf/edex/plugin/redbook/menu/NdmMenuConverter.java @@ -253,7 +253,7 @@ public class NdmMenuConverter { for (String line : productButtonKeys) { line = line.trim(); // Skip comment lines - if (line.startsWith("#")) { + if (line.startsWith("#") || line.trim().length() == 0) { continue; } @@ -276,7 +276,7 @@ public class NdmMenuConverter { for (String line : dataKeys) { line = line.trim(); // Skip comment lines - if (line.startsWith("#")) { + if (line.startsWith("#") || line.trim().length() == 0) { continue; } String[] parts = line.split("\\|"); From 9b9b6e41926b166438bc7822f83340be69f5aa3d Mon Sep 17 00:00:00 2001 From: "Shawn.Hooper" Date: Wed, 4 Feb 2015 21:12:44 -0500 Subject: [PATCH 10/34] ASM #17069 - Checkin rebuilt library.ohd.pproc.so Change-Id: Iacff8a6237f75a5d98fbd1b5c8a6f606b4be04de Former-commit-id: da357f98ee1e129d6d1734d6fafa5b285937e693 [formerly 73e910edadfc33b19701ae5e80089e6ac7acf6cb] Former-commit-id: a4c5e17c3a04f92ff7daa58d32f6d23659df0a3c --- .../lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id | 2 +- .../edex/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/nativeLib/files.native/awipsShare/hydroapps/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id b/nativeLib/files.native/awipsShare/hydroapps/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id index 85d33f3b5d..f53ec0e033 100644 --- a/nativeLib/files.native/awipsShare/hydroapps/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id +++ b/nativeLib/files.native/awipsShare/hydroapps/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id @@ -1 +1 @@ -198d9e1b995f9fcf55686c0992f4bb0612f429e0 \ No newline at end of file +409f53ec4cfa407ad0854b18dafce4521e2bb99a \ No newline at end of file diff --git a/nativeLib/files.native/edex/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id b/nativeLib/files.native/edex/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id index 85d33f3b5d..f53ec0e033 100644 --- a/nativeLib/files.native/edex/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id +++ b/nativeLib/files.native/edex/lib/native/linux32/library.ohd.pproc.so.REMOVED.git-id @@ -1 +1 @@ -198d9e1b995f9fcf55686c0992f4bb0612f429e0 \ No newline at end of file +409f53ec4cfa407ad0854b18dafce4521e2bb99a \ No newline at end of file From c989ac6008b7f27262f5e38bb94f1eaa9db9c490 Mon Sep 17 00:00:00 2001 From: Chin Chen Date: Tue, 3 Feb 2015 11:03:14 -0500 Subject: [PATCH 11/34] VLab Issue #6278 - Model soundings being interpolated below the surface for elevated sites Change-Id: I4acf7182250ecaad1893432296069e54f62d458c Former-commit-id: 8c358622a7f077e46bb0c1521465d2790c1f743d [formerly 66e8f998d8d400d3d4ba736a2826b5e22a3f174b] Former-commit-id: 5d2b504d0330f299045e0bee87b8f622d3fbc2b8 --- .../META-INF/MANIFEST.MF | 1 + .../tasks/profile/MdlSoundingQuery.java | 2241 +++++++++-------- .../display/rsc/NsharpSkewTPaneResource.java | 36 +- 3 files changed, 1208 insertions(+), 1070 deletions(-) diff --git a/ncep/gov.noaa.nws.ncep.edex.uengine/META-INF/MANIFEST.MF b/ncep/gov.noaa.nws.ncep.edex.uengine/META-INF/MANIFEST.MF index 77c9afff10..2c6fb20302 100644 --- a/ncep/gov.noaa.nws.ncep.edex.uengine/META-INF/MANIFEST.MF +++ b/ncep/gov.noaa.nws.ncep.edex.uengine/META-INF/MANIFEST.MF @@ -33,6 +33,7 @@ Import-Package: com.raytheon.uf.common.dataplugin.bufrua, com.raytheon.uf.common.dataplugin.level, com.raytheon.uf.common.pointdata, com.raytheon.uf.common.pointdata.spatial, + com.raytheon.uf.common.topo, com.raytheon.uf.edex.pointdata, gov.noaa.nws.ncep.common.tools, gov.noaa.nws.ncep.edex.common.metparameters, diff --git a/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MdlSoundingQuery.java b/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MdlSoundingQuery.java index 18b8febe91..65d0430497 100644 --- a/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MdlSoundingQuery.java +++ b/ncep/gov.noaa.nws.ncep.edex.uengine/src/gov/noaa/nws/ncep/edex/uengine/tasks/profile/MdlSoundingQuery.java @@ -1,1057 +1,1184 @@ -package gov.noaa.nws.ncep.edex.uengine.tasks.profile; - -/** - * - * gov.noaa.nws.ncep.edex.uengine.tasks.profile.MdlSoundingQuery - * - * This java class performs the Grid model sounding data query functions. - * This code has been developed by the SIB for use in the AWIPS2 system. - * - *
- * SOFTWARE HISTORY
- * 
- * Date         Ticket#    	Engineer    Description
- * -------		------- 	-------- 	-----------
- * 04/04/2011	301			Chin Chen	Initial coding
- * 02/28/2012               Chin Chen   modify several sounding query algorithms for better performance
- * 03/28/2012               Chin Chen   Add new API to support query multiple Points at one shoot and using
- * 										dataStore.retrieveGroups()
- * Oct 15, 2012 2473        bsteffen    Remove ncgrib
- * 03/2014		1116		T. Lee		Added DpD
- * 01/2015      DR#16959    Chin Chen   Added DpT support to fix DR 16959 NSHARP freezes when loading a sounding from 
- *                                      HiRes-ARW/NMM models
- * 
- * - * @author Chin Chen - * @version 1.0 - */ -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingModel; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingProfile; -import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingTimeLines; - -import java.awt.Point; -import java.util.ArrayList; -import java.util.List; - -import javax.measure.converter.UnitConverter; -import javax.measure.unit.NonSI; -import javax.measure.unit.SI; - -import org.geotools.coverage.grid.GridGeometry2D; -import org.geotools.geometry.GeneralDirectPosition; -import org.opengis.geometry.DirectPosition; -import org.opengis.referencing.FactoryException; -import org.opengis.referencing.crs.CoordinateReferenceSystem; -import org.opengis.referencing.operation.MathTransform; - -import com.raytheon.edex.uengine.tasks.query.TableQuery; -import com.raytheon.uf.common.dataplugin.PluginException; -import com.raytheon.uf.common.dataplugin.grid.GridConstants; -import com.raytheon.uf.common.dataplugin.grid.GridInfoRecord; -import com.raytheon.uf.common.dataplugin.grid.GridRecord; -import com.raytheon.uf.common.geospatial.ISpatialObject; -import com.raytheon.uf.common.geospatial.MapUtil; -import com.raytheon.uf.common.geospatial.PointUtil; -import com.raytheon.uf.edex.database.DataAccessLayerException; -import com.raytheon.uf.edex.database.dao.CoreDao; -import com.raytheon.uf.edex.database.dao.DaoConfig; -import com.raytheon.uf.edex.database.query.DatabaseQuery; -import com.vividsolutions.jts.geom.Coordinate; -import com.vividsolutions.jts.geom.CoordinateSequence; -import com.vividsolutions.jts.geom.Geometry; -import com.vividsolutions.jts.geom.GeometryFactory; -import com.vividsolutions.jts.geom.LinearRing; -import com.vividsolutions.jts.geom.Polygon; -import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; - -//import org.opengis.geometry.Envelope; - -public class MdlSoundingQuery { - private static final String GRID_TBL_NAME = "grid"; - - private static String GRID_PARMS = "GH, uW, vW,T, DWPK, SPFH,OMEG, RH, DpD, DpT"; - - private enum GridParmNames { - GH, uW, vW, T, DWPK, SPFH, OMEG, RH, DpD, DpT - }; - - public static UnitConverter kelvinToCelsius = SI.KELVIN - .getConverterTo(SI.CELSIUS); - - private static final UnitConverter metersPerSecondToKnots = SI.METERS_PER_SECOND - .getConverterTo(NonSI.KNOT); - - // Note; we are using NCInventory now. So, this api is actually not used. - public static NcSoundingTimeLines getMdlSndTimeLine(String mdlType, - String currentDBTblName) { - NcSoundingTimeLines tl = new NcSoundingTimeLines(); - /* - * if(currentDBTblName.equals(NCGRIB_TBL_NAME)){ Object[] refTimeAry = - * null; String queryStr = new String("Select Distinct reftime FROM " + - * currentDBTblName + " where modelname='" + mdlType + - * "' ORDER BY reftime DESC"); - * - * CoreDao dao = new CoreDao(DaoConfig.forClass(NcgribRecord.class)); - * refTimeAry = (Object[]) dao.executeSQLQuery(queryStr); - * tl.setTimeLines(refTimeAry); }else - * if(currentDBTblName.equals(D2DGRIB_TBL_NAME)){ TableQuery query; try - * { query = new TableQuery("metadata", GridRecord.class.getName()); - * query.setDistinctField("dataTime.refTime"); - * query.addParameter(GridConstants.DATASET_ID, mdlType); - * query.setSortBy("dataTime.refTime", false); - * - * @SuppressWarnings("unchecked") List recList = - * (List) query.execute(); - * tl.setTimeLines(recList.toArray()); } catch (DataAccessLayerException - * e) { // TODO Auto-generated catch block e.printStackTrace(); } catch - * (Exception e) { // TODO Auto-generated catch block - * e.printStackTrace(); } - * - * } - */ - - // Chin: modified for Unified Grid DB - // Use the following SQL statement - // Select Distinct reftime FROM grid FULL JOIN grid_info ON - // grid.info_id=grid_info.id where grid_info.datasetid='gfs' ORDER BY - // reftime DESC - Object[] refTimeAry = null; - String queryStr = new String( - "Select Distinct reftime FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id where grid_info.datasetid='" - + mdlType + "' ORDER BY reftime DESC"); - - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - refTimeAry = dao.executeSQLQuery(queryStr); - tl.setTimeLines(refTimeAry); - - return tl; - } - - public static NcSoundingTimeLines getMdlSndRangeTimeLine(String mdlType, - String refTimeStr, String currentDBTblName) { - NcSoundingTimeLines tl = new NcSoundingTimeLines(); - /* - * if(currentDBTblName.equals(NCGRIB_TBL_NAME)){ Object[] refTimeAry = - * null; String queryStr = new String("Select Distinct rangestart FROM " - * + currentDBTblName + " where modelname='" + mdlType + "' AND " + - * "reftime='" + refTimeStr + ":00:00'" + " ORDER BY rangestart"); - * System.out.println("queryStr " + queryStr); - * - * CoreDao dao = new CoreDao(DaoConfig.forClass(SoundingSite.class)); - * refTimeAry = (Object[]) dao.executeSQLQuery(queryStr); - * tl.setTimeLines(refTimeAry); } else - * if(currentDBTblName.equals(D2DGRIB_TBL_NAME)){ TableQuery query; try - * { query = new TableQuery("metadata", GridRecord.class.getName()); - * query.setDistinctField("dataTime.validPeriod.start"); - * query.addParameter(GridConstants.DATASET_ID, mdlType); - * query.addParameter("dataTime.refTime", refTimeStr + ":00:00"); - * query.setSortBy("dataTime.validPeriod.start", true); - * - * @SuppressWarnings("unchecked") List recList = - * (List) query.execute(); - * tl.setTimeLines(recList.toArray()); } catch (DataAccessLayerException - * e) { // TODO Auto-generated catch block e.printStackTrace(); } catch - * (Exception e) { // TODO Auto-generated catch block - * e.printStackTrace(); } - */ - // Chin: modified for Unified Grid DB - // make sure data in DB is not just nHour data, as those data are not - // used by Nsharp. And when query to it, the returned will be - // null. We do not want to show such sounding time line to user. - // use this SQL query string for gfs as example. - /* - * Select Distinct rangestart FROM grid FULL JOIN grid_info ON - * grid.info_id=grid_info.id where grid.reftime = '2012-01-26 00:00:00' - * AND grid.rangestart = grid.rangeend AND - * grid_info.datasetid='mesoEta212' AND - * grid_info.parameter_abbreviation='T' order by rangestart - */ - Object[] soundingTimeAry = null; - List reSoundingTimeAry = new ArrayList(); - String queryStr = new String( - "Select Distinct rangestart FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id where grid.reftime = '" - + refTimeStr - + ":00:00' AND grid.rangestart = grid.rangeend AND grid_info.datasetid='" - + mdlType - + "' AND grid_info.parameter_abbreviation='T' order by rangestart"); - // System.out.println("queryStr " + queryStr); - - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - soundingTimeAry = (Object[]) dao.executeSQLQuery(queryStr); - for (int i = 0; i < soundingTimeAry.length; i++) { - /* - * Chin: make sure the time line has more than 5 T(temp) values at - * pressure (levelone) greater/equal than/to 100 hPa (mbar) use this - * SQL : Select count(rangestart) FROM (select rangestart FROM grid - * FULL JOIN grid_info ON grid.info_id=grid_info.id FULL JOIN level - * ON grid_info.level_id= level.id where grid.rangestart = - * '2012-01-26 03:00:00.0' AND grid.rangestart = grid.rangeend AND - * grid_info.datasetid='mesoEta212' AND - * grid_info.parameter_abbreviation='T' AND level.levelonevalue > - * 99) X HAVING count(X.rangestart) >5 - */ - String queryStr1 = new String( - "Select count(rangestart) FROM (select rangestart FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id FULL JOIN level ON grid_info.level_id= level.id where grid.rangestart = '" - + soundingTimeAry[i] - + "' AND grid.rangestart = grid.rangeend AND grid_info.datasetid='" - + mdlType - + "' AND grid_info.parameter_abbreviation='T' AND level.levelonevalue > 99) X HAVING count(X.rangestart) >2"); - Object[] countAry = null; - // System.out.println("queryStr1 " + queryStr1); - countAry = (Object[]) dao.executeSQLQuery(queryStr1); - java.math.BigInteger count = new java.math.BigInteger("0"); - if (countAry.length > 0) { - // System.out.println("rangestart =" - // +soundingTimeAry[i]+" number="+countAry[0]); - count = (java.math.BigInteger) countAry[0]; - } - // else{ - // System.out.println("rangestart =" - // +soundingTimeAry[i]+" return null"); - // } - if (count.intValue() > 2) { - Object timeLine = soundingTimeAry[i]; - reSoundingTimeAry.add(timeLine); - } - } - - tl.setTimeLines(reSoundingTimeAry.toArray()); - - // } - return tl; - } // public static NcSoundingProfile getMdlSndData(double lat, double lon, - - // String stn, long refTimeL, long validTimeL, String sndTypeStr, - // SndQueryKeyType queryType, String mdlName) { - // //*System.out.println("getPfcSndData input ref time = "+ - // refTimeL+" valid time is " + validTimeL); - // Calendar refTimeCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); - // refTimeCal.setTimeInMillis(refTimeL); - // Calendar validTimeCal = - // Calendar.getInstance(TimeZone.getTimeZone("GMT")); - // validTimeCal.setTimeInMillis(validTimeL); - // return getMdlSndData( lat, lon, refTimeCal, validTimeCal, "ncgrib", - // mdlName); - // } - - /** - * Returns a list of profile for location (lat,lon) array, time, and model - * for grib or ncgrib data. - * - * @param double[][] latLonArray, e.g. at nth element, lat=[n][0], - * lon=[n][1] - * @param refTimeCal - * data record reference time - * @param validTimeCal - * data record valid time - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param mdlName - * the name of the model - * @return the profile created @ 3/28/2012 - */ - public static List getMdlSndDataProfileList( - double[][] latLonArray, String refTime, String validTime, - String pluginName, String mdlName) { - double lat, lon; - // System.out.println("getMdlSndData lat=" + lat + " lon="+lon); - long t01 = System.currentTimeMillis(); - NcSoundingProfile pf = new NcSoundingProfile(); - // NcSoundingCube cube = new NcSoundingCube(); - List soundingProfileList = new ArrayList(); - List levels = getModelLevels(refTime, validTime, pluginName, mdlName); - if (levels.size() == 0) { - System.out.println("getModelLevels return 0; file=" + refTime - + " stime=" + validTime + " gribtype=" + pluginName - + " modeltype=" + mdlName); - return soundingProfileList; - } - // System.out.println("getModelLevels = "+ - // levels.size()+" levels, took "+ (System.currentTimeMillis()-t01) + - // " ms"); - - List points = new ArrayList(); - for (int k = 0; k < latLonArray.length; k++) { - lat = latLonArray[k][0]; - lon = latLonArray[k][1]; - Point pnt = getLatLonIndices(lat, lon, refTime, validTime, levels - .get(0).toString(), pluginName, mdlName); - if (pnt == null) { - System.out.println("getLatLonIndices return 0; lat=" + lat - + " lon=" + lon + " stime=" + validTime + " gribtype=" - + pluginName + " modeltype=" + mdlName); - } else { - points.add(pnt); - } - } - if (points.size() == 0) { - return soundingProfileList; - } - long t011 = System.currentTimeMillis(); - soundingProfileList = queryProfileListByPointGroup(points, refTime, - validTime, pluginName, mdlName, levels); - System.out.println("queryProfileListByPointGroup took " - + (System.currentTimeMillis() - t011) + " ms"); - - return soundingProfileList; - /* - * The floowing should be done in queryProfileListByPointGroup() - * //System.out.println("getModelSoundingLayerList= "+ layerList.size()+ - * " layers, took "+ (System.currentTimeMillis()-t012) + " ms"); - * //pf.setStationLatitude( lat); //pf.setStationLongitude( lon); - * //Float sfcPressure = getModelSfcPressure(pnt, refTime, validTime, // - * pluginName, mdlName); - * //System.out.println("getModelSfcPressure took "+ - * (System.currentTimeMillis()-t013) + " ms"); //if (sfcPressure == - * null) { // pf.setSfcPress(-9999.f); //} //else { // if - * (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) // - * pf.setSfcPress(sfcPressure/100F); // else // - * pf.setSfcPress(sfcPressure); //} - * //System.out.println("surface pressure ="+pf.getSfcPress()+ - * " lat= "+lat+ " lon="+lon); //calculate dew point if necessary long - * t014 = System.currentTimeMillis(); MergeSounding ms = new - * MergeSounding(); //ms.spfhToDewpoint(layerList); - * ms.rhToDewpoint(layerList); System.out.println("MergeSounding took "+ - * (System.currentTimeMillis()-t014) + " ms"); - * - * - * pf.setSoundingLyLst(layerList); - * - * - * soundingProfileList.add(pf); - * //cube.setSoundingProfileList(soundingProfileList); - * //cube.setRtnStatus(NcSoundingCube.QueryStatus.OK); long t02 = - * System.currentTimeMillis(); - * System.out.println("MDL cube retreival took " + (t02 - t01)); return - * pf; - */ - - } - - /** - * Returns a profile for a specified location (lat,lon), time, and model for - * grib or ncgrib data. - * - * @param lat - * location latitude - * @param lon - * location longitude - * @param refTimeCal - * data record reference time - * @param validTimeCal - * data record valid time - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param mdlName - * the name of the model - * @return the profile - * - * public static NcSoundingProfile getMdlSndData(double lat, double - * lon, String refTime, String validTime, String pluginName, String - * mdlName) { System.out.println("getMdlSndData lat=" + lat + - * " lon="+lon); long t01 = System.currentTimeMillis(); - * NcSoundingProfile pf = new NcSoundingProfile(); List levels = - * getModelLevels(refTime, validTime, pluginName, mdlName); if - * (levels.size() == 0) { - * System.out.println("getModelLevels return 0; file=" + refTime+ - * " stime="+validTime + " gribtype="+ pluginName + - * " modeltype="+mdlName); - * pf.setRtnStatus(NcSoundingCube.QueryStatus.FAILED); return pf; } - * System.out.println("getModelLevels = "+ - * levels.size()+" levels, took "+ (System.currentTimeMillis()-t01) - * + " ms"); long t011 = System.currentTimeMillis(); Point pnt = - * getLatLonIndices(lat, lon, refTime, validTime, - * levels.get(0).toString(), pluginName, mdlName); if (pnt == null) - * { System.out.println("getLatLonIndices return 0; lat=" + lat + - * " lon="+lon+" stime="+validTime + " gribtype="+ pluginName + - * " modeltype="+mdlName); - * - * pf.setRtnStatus(NcSoundingCube.QueryStatus.LOCATION_NOT_FOUND); - * return pf; - * - * } System.out.println("getLatLonIndices pntX=" + pnt.getX()+ - * " pntY=" + pnt.getY()+ " took "+ - * (System.currentTimeMillis()-t011) + " ms"); long t012 = - * System.currentTimeMillis(); List layerList = - * getModelSoundingLayerList(pnt, refTime, validTime, pluginName, - * mdlName, levels); if (layerList.size() == 0) { - * System.out.println("getModelSoundingLayerList return 0; lat=" + - * lat + " lon="+lon+" stime="+validTime + " gribtype="+ pluginName - * + " modeltype="+mdlName); - * - * - * pf.setRtnStatus(NcSoundingCube.QueryStatus.FAILED); return pf; } - * - * System.out.println("getModelSoundingLayerList= "+ - * layerList.size()+ " layers, took "+ - * (System.currentTimeMillis()-t012) + " ms"); - * - * pf.setStationLatitude( lat); pf.setStationLongitude( lon); Float - * sfcPressure = getModelSfcPressure(pnt, refTime, validTime, - * pluginName, mdlName); - * //System.out.println("getModelSfcPressure took "+ - * (System.currentTimeMillis()-t013) + " ms"); if (sfcPressure == - * null) { pf.setSfcPress(-9999.f); } else { if - * (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) - * pf.setSfcPress(sfcPressure/100F); else - * pf.setSfcPress(sfcPressure); } - * //System.out.println("surface pressure ="+pf.getSfcPress()+ - * " lat= "+lat+ " lon="+lon); //calculate dew point if necessary - * long t014 = System.currentTimeMillis(); MergeSounding ms = new - * MergeSounding(); //ms.spfhToDewpoint(layerList); - * ms.rhToDewpoint(layerList); - * System.out.println("MergeSounding took "+ - * (System.currentTimeMillis()-t014) + " ms"); - * - * - * pf.setSoundingLyLst(layerList); - * - * long t02 = System.currentTimeMillis(); - * System.out.println("MDL cube retreival took " + (t02 - t01)); - * return pf; - * - * } - */ - - public static NcSoundingModel getMdls(String pluginName) { - NcSoundingModel mdls = new NcSoundingModel(); - Object[] mdlName = null; - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridInfoRecord.class)); - String queryStr = new String( - "Select Distinct modelname FROM grib_models ORDER BY modelname"); - mdlName = (Object[]) dao.executeSQLQuery(queryStr); - } - if (mdlName != null && mdlName.length > 0) { - List mdlList = new ArrayList(); - for (Object mn : mdlName) { - mdlList.add((String) mn); - } - mdls.setMdlList(mdlList); - } - return mdls; - } - - public static boolean isPointWithinGridGeometry(double lat, double lon, - String refTime, String validTime, String pluginName, - String modelName) { - - ISpatialObject spatialArea = null; - MathTransform crsFromLatLon = null; - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); - - query.setMaxResults(new Integer(1)); - query.addQueryParam(GridConstants.DATASET_ID, modelName); - query.addQueryParam("dataTime.refTime", refTime); - query.addQueryParam("dataTime.validPeriod.start", validTime); - - try { - List recList = ((List) dao - .queryByCriteria(query)); - if (recList.size() == 0) { - return false; - } else { - GridRecord rec = recList.get(0); - spatialArea = rec.getSpatialObject(); - } - - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } - } - - try { - crsFromLatLon = MapUtil - .getTransformFromLatLon(spatialArea.getCrs()); - } catch (FactoryException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - - DirectPosition lowerCorner = MapUtil.getGridGeometry(spatialArea) - .getEnvelope().getLowerCorner(); - DirectPosition upperCorner = MapUtil.getGridGeometry(spatialArea) - .getEnvelope().getUpperCorner(); - - GeometryFactory gf = new GeometryFactory(); - - Coordinate p1 = new Coordinate(lowerCorner.getOrdinate(0), - lowerCorner.getOrdinate(1)); - Coordinate p2 = new Coordinate(lowerCorner.getOrdinate(0), - upperCorner.getOrdinate(1)); - Coordinate p3 = new Coordinate(upperCorner.getOrdinate(0), - upperCorner.getOrdinate(1)); - Coordinate p4 = new Coordinate(upperCorner.getOrdinate(0), - lowerCorner.getOrdinate(1)); - - LinearRing lr = gf.createLinearRing(new Coordinate[] { p1, p2, p3, p4, - p1 }); - - Polygon gridGeometry = gf.createPolygon(lr, null); - - DirectPosition ll = new GeneralDirectPosition(MapUtil.LATLON_PROJECTION); - - Coordinate coord = new Coordinate(lon, lat); - ll.setOrdinate(0, coord.x); - ll.setOrdinate(1, coord.y); - // DirectPosition crs = new GeneralDirectPosition(spatialArea.getCrs()); - // try { - // crsFromLatLon.transform(ll, crs); - // } catch (MismatchedDimensionException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } catch (TransformException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - // - // Coordinate newC = new Coordinate(crs.getOrdinate(0), - // crs.getOrdinate(1)); - Coordinate newC = new Coordinate(ll.getOrdinate(0), ll.getOrdinate(1)); - - com.vividsolutions.jts.geom.Point p = gf.createPoint(newC); - - return gridGeometry.contains(p); - - } - - public static boolean isPointWithinGridGeometry2(double lat, double lon, - String refTime, String validTime, String pluginName, - String modelName) { - - ISpatialObject spatialArea = null; - MathTransform crsFromLatLon = null; - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); - - query.setMaxResults(new Integer(1)); - query.addQueryParam(GridConstants.DATASET_ID, modelName); - query.addQueryParam("dataTime.refTime", refTime); - query.addQueryParam("dataTime.validPeriod.start", validTime); - - try { - List recList = ((List) dao - .queryByCriteria(query)); - if (recList.size() == 0) { - return false; - } else { - GridRecord rec = recList.get(0); - spatialArea = rec.getSpatialObject(); - } - - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return false; - } - } - - Geometry g = spatialArea.getGeometry(); - - GeometryFactory geometryFactory = new GeometryFactory(); - CoordinateSequence sequence = new CoordinateArraySequence( - g.getCoordinates()); - - Coordinate[] oldCoords = sequence.toCoordinateArray(); - Coordinate[] newCoords = new Coordinate[oldCoords.length]; - /* - * adjust longitude for global grids whose lon span goes from 0 to 360 - * and the asked lon is negative. - */ - for (Coordinate c : oldCoords) { - double x = c.x; - double y = c.y; - double z = c.z; - if (x >= 180.0 && x <= 360.0 && lon < 0.0) { - lon = lon + 360.0; - break; - } - } - Coordinate coord = new Coordinate(lon, lat); - - LinearRing ring = new LinearRing(sequence, geometryFactory); - Polygon gridGeometry = new Polygon(ring, null, geometryFactory); - com.vividsolutions.jts.geom.Point p = geometryFactory - .createPoint(coord); - - return gridGeometry.contains(p); - - } - - /** - * Returns the value of surface pressure for a specified location, time, and - * model for grib or ncgrib data. - * - * @param pnt - * location - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param modelName - * the name of the model - * @return surface pressure - */ - public static Float getModelSfcPressure(Point pnt, String refTime, - String validTime, String pluginName, String modelName) { - - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); - - query.addQueryParam(GridConstants.LEVEL_ONE, "0.0"); - query.addQueryParam(GridConstants.LEVEL_TWO, "-999999.0"); - query.addQueryParam(GridConstants.MASTER_LEVEL_NAME, "MSL"); - query.addQueryParam(GridConstants.PARAMETER_ABBREVIATION, "PMSL"); - query.addQueryParam(GridConstants.DATASET_ID, modelName); - query.addQueryParam("dataTime.refTime", refTime); - query.addQueryParam("dataTime.validPeriod.start", validTime); - - GridRecord rec = null; - try { - List recList = ((List) dao - .queryByCriteria(query)); - if (recList.size() == 0) { - return null; - } else { - rec = recList.get(0); - PointIn pointIn = new PointIn(pluginName, rec, pnt.x, pnt.y); - try { - float fdata = pointIn.getPointData(); - return new Float(fdata); - } catch (PluginException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - } - - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - } - return null; - - } - - /** - * Returns a list of NcSoundingProfile for a group of Point with specific - * ref and range time, and model for grib or ncgrib data. - * - * @param pnt - * location - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param modelName - * the name of the model - * @param levels - * list of vertical levels - * @return list of NcSoundingLayer objects - * - * Created @ 3/28/2012 - */ - - private static List queryProfileListByPointGroup( - List points, String refTime, String validTime, - String pluginName, String modelName, List levels) { - - List soundingProfileList = new ArrayList(); - List fdataArrayList = new ArrayList(); - // long t01 = System.currentTimeMillis(); - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - List recList = new ArrayList(); - ; - TableQuery query; - try { - query = new TableQuery("metadata", GridRecord.class.getName()); - query.addParameter(GridConstants.MASTER_LEVEL_NAME, "MB"); - query.addParameter(GridConstants.DATASET_ID, modelName); - query.addList(GridConstants.PARAMETER_ABBREVIATION, GRID_PARMS); - query.addParameter("dataTime.refTime", refTime); - query.addParameter("dataTime.validPeriod.start", validTime); - query.setSortBy(GridConstants.LEVEL_ONE, false); - recList = (List) query.execute(); - if (recList.size() != 0) { - PointIn pointIn = new PointIn(pluginName, recList.get(0)); - fdataArrayList = pointIn.getHDF5GroupDataPoints( - recList.toArray(), points); - } - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - int index = 0; - GridGeometry2D geom = MapUtil.getGridGeometry(spatialArea); - CoordinateReferenceSystem crs = geom.getCoordinateReferenceSystem(); - Coordinate coord = new Coordinate(45, 45); - - for (float[] fdataArray : fdataArrayList) { - // one fdataArray is for one Point or say one profile - NcSoundingProfile pf = new NcSoundingProfile(); - List soundLyList = new ArrayList(); - Point pnt = points.get(index); - Object[] recArray = recList.toArray(); - for (Object level : levels) { - NcSoundingLayer soundingLy = new NcSoundingLayer(); - double pressure = (Double) level; - soundingLy.setPressure((float) pressure); - for (int i = 0; i < recArray.length; i++) { - GridRecord rec1 = (GridRecord) recArray[i]; - float fdata = fdataArray[i]; - if (rec1.getLevel().getLevelonevalue() == pressure) { - String prm = rec1.getParameter().getAbbreviation(); - //System.out.println("prm="+prm+" value="+fdata); - switch (GridParmNames.valueOf(prm)) { - case GH: - soundingLy.setGeoHeight(fdata); - break; - case uW: - // HDF5 data in unit of m/s, convert to Knots - // 4/12/2012 - soundingLy - .setWindU((float) metersPerSecondToKnots - .convert(fdata)); - break; - case vW: - // HDF5 data in unit of m/s, convert to Knots - // 4/12/2012 - soundingLy - .setWindV((float) metersPerSecondToKnots - .convert(fdata)); - break; - case T: - soundingLy - .setTemperature((float) kelvinToCelsius - .convert(fdata)); - break; - case DWPK: - soundingLy.setDewpoint((float) kelvinToCelsius - .convert(fdata)); - break; - case OMEG: - soundingLy.setOmega(fdata); - break; - case RH: - soundingLy.setRelativeHumidity(fdata); - break; - case DpD: - soundingLy.setDpd(fdata); - break; - case DpT: - soundingLy.setDewpoint((float) kelvinToCelsius - .convert(fdata)); - break; - case SPFH: - soundingLy.setSpecHumidity(fdata); - break; - default: - break; - } - } - } - soundLyList.add(soundingLy); - } - try { - coord = PointUtil.determineLatLon(pnt, crs, geom); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - // System.out.println(" point coord.y="+coord.y+ " coord.x="+ - // coord.x); - pf.setStationLatitude(coord.y); - pf.setStationLongitude(coord.x); - // Float sfcPressure = getModelSfcPressure(pnt, refTime, - // validTime, - // pluginName, modelName); - // System.out.println("getModelSfcPressure took "+ - // (System.currentTimeMillis()-t013) + " ms"); - // /if (sfcPressure == null) { - pf.setSfcPress(-9999.f); - // } - // else { - // pf.setSfcPress(sfcPressure/100F); - // } - // System.out.println("surface pressure ="+pf.getSfcPress()+ - // " lat= "+lat+ " lon="+lon); - // calculate dew point if necessary - MergeSounding ms = new MergeSounding(); - // ms.spfhToDewpoint(layerList); - ms.rhToDewpoint(soundLyList); - ms.dpdToDewpoint(soundLyList); - pf.setSoundingLyLst(soundLyList); - soundingProfileList.add(pf); - index++; - } - } - return soundingProfileList; - } - - /** - * Returns a list of NcSoundingLayer for a specified location, time, and - * model for grib or ncgrib data. - * - * @param pnt - * location - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param modelName - * the name of the model - * @param levels - * list of vertical levels - * @return list of NcSoundingLayer objects - * - * - * private static List - * getModelSoundingLayerList(Point pnt, String refTime, String - * validTime, String pluginName, String modelName, List levels) { - * List soundLyList = new - * ArrayList(); - * - * //long t01 = System.currentTimeMillis(); if - * (pluginName.equalsIgnoreCase(NCGRIB_TBL_NAME)) { - * - * TableQuery query; try { query = new TableQuery("metadata", - * NcgribRecord.class.getName()); query.addParameter("vcord", - * "PRES"); query.addParameter("modelName", modelName); - * query.addList("parm",NC_PARMS);//parmList.toString()); // - * query.addParameter("dataTime.refTime", refTime); - * query.addParameter("dataTime.validPeriod.start", validTime); - * //query.addParameter("glevel1", level.toString()); - * query.setSortBy("glevel1", false); - * - * - * List recList = (List) - * query.execute(); - * System.out.println("Ncgrib group query0 result size ="+ - * recList.size()); - * - * if (recList.size() != 0) { - * - * PointIn pointIn = new PointIn(pluginName, recList.get(0), pnt.x, - * pnt.y); //Chin note: // We query all levels (pressure) and all - * parameters (at that level) at once. // The return array - * (fdataArray) are listed in the same order as query array - * (recList.toArray()) //However, returned array does not tell you - * which parameter itself is. //Therefore, we have to use - * information in query array to find out returned value's type - * (which parameter it is) // Further, we have to sort and store - * returned values to NcSoundingLayer based on its level (pressure) - * // Parameters in same level should be stored in one same - * NcSoundingLayer float[] fdataArray = - * pointIn.getHDF5GroupDataPoint(recList.toArray()); Object[] - * recArray = recList.toArray(); for (Object level : levels){ - * NcSoundingLayer soundingLy = new NcSoundingLayer(); int pressure= - * (Integer)level; soundingLy.setPressure( pressure); - * - * for (int i=0; i < recArray.length; i++) { NcgribRecord rec1 = - * (NcgribRecord)recArray[i]; float fdata = fdataArray[i]; - * if(rec1.getGlevel1() == pressure){ String prm = rec1.getParm(); - * //System.out.println("point.x="+ pnt.x + - * " .y="+pnt.y+"pressure="+rec1 // .getGlevel1()+ " Parm="+prm ); - * //long t01 = System.currentTimeMillis(); switch - * (NcParmNames.valueOf(prm)) { case HGHT: - * soundingLy.setGeoHeight(fdata); break; case UREL: // HDF5 data in - * unit of Knots, no conversion needed soundingLy.setWindU(fdata); - * break; case VREL: // HDF5 data in unit of Knots, no conversion - * needed soundingLy.setWindV(fdata); break; case TMPK: - * soundingLy.setTemperature((float) kelvinToCelsius - * .convert(fdata)); break; case DWPK: - * soundingLy.setDewpoint((float) kelvinToCelsius .convert(fdata)); - * break; case SPFH: soundingLy.setSpecHumidity(fdata); break; case - * OMEG: soundingLy.setOmega(fdata); break; case RELH: - * soundingLy.setRelativeHumidity(fdata); break; } } } - * soundLyList.add(soundingLy); } } - * - * } catch (DataAccessLayerException e) { // TODO Auto-generated - * catch block e.printStackTrace(); } catch (Exception e) { // TODO - * Auto-generated catch block e.printStackTrace(); } - * //System.out.println("getModelSoundingLayerList:total level = "+ - * totalLevel + " total records= "+totalRecords ); - * - * } else if (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) { try { - * TableQuery query = new TableQuery("metadata", - * GribRecord.class.getName()); - * //query.addParameter("modelInfo.level.levelonevalue", // - * level.toString()); - * //query.addParameter("modelInfo.level.leveltwovalue", // - * "-999999.0"); - * query.addParameter("modelInfo.level.masterLevel.name", "MB"); - * query.addParameter("modelInfo.modelName", modelName); - * query.addList("modelInfo.parameterAbbreviation", D2D_PARMS); - * query.addParameter("dataTime.refTime", refTime); - * query.addParameter("dataTime.validPeriod.start", validTime); - * query.setSortBy("modelInfo.level.levelonevalue", false); - * //System.out.println("level = "+ level.toString()); - * - * List recList = (List) query.execute(); - * System.out.println("Grib group query0 result size ="+ - * recList.size()); - * - * if (recList.size() > 0) { PointIn pointIn = new - * PointIn(pluginName, recList.get(0), pnt.x, pnt.y); float[] - * fdataArray = pointIn.getHDF5GroupDataPoint(recList.toArray()); - * Object[] recArray = recList.toArray(); for (Object level : - * levels){ NcSoundingLayer soundingLy = new NcSoundingLayer(); - * double pressure= (Double)level; soundingLy.setPressure( - * (float)pressure); - * - * for (int i=0; i < recArray.length; i++) { GribRecord rec1 = - * (GribRecord)recArray[i]; float fdata = fdataArray[i]; - * if(rec1.getModelInfo().getLevelOneValue() == pressure){ String - * prm = rec1.getModelInfo().getParameterAbbreviation(); - * //System.out.println("point.x="+ pnt.x + - * " .y="+pnt.y+"pressure="+pressure+ " Parm="+prm ); //long t01 = - * System.currentTimeMillis(); switch (D2DParmNames.valueOf(prm)) { - * case GH: soundingLy.setGeoHeight(fdata); break; case uW: // HDF5 - * data in unit of Knots, no conversion needed - * soundingLy.setWindU(fdata); break; case vW: // HDF5 data in unit - * of Knots, no conversion needed soundingLy.setWindV(fdata); break; - * case T: soundingLy.setTemperature((float) kelvinToCelsius - * .convert(fdata)); break; case DWPK: - * soundingLy.setDewpoint((float) kelvinToCelsius .convert(fdata)); - * break; case OMEG: soundingLy.setOmega(fdata); break; case RH: - * soundingLy.setRelativeHumidity(fdata); break; } } } - * soundLyList.add(soundingLy); } } } catch - * (DataAccessLayerException e) { // TODO Auto-generated catch block - * e.printStackTrace(); } catch (Exception e) { // TODO - * Auto-generated catch block e.printStackTrace(); } } - * - * //long t02 = System.currentTimeMillis(); - * //System.out.println("MDL profile retreival took " + (t02 - - * t01)); - * - * for(NcSoundingLayer layer: soundLyList){ - * System.out.println("pre="+ layer.getPressure()+ - * " h="+layer.getGeoHeight()+ " T="+layer.getTemperature()+" D="+ - * layer.getDewpoint()+ " WS="+layer.getWindSpeed()+ - * " WD="+layer.getWindDirection() + " SH="+layer.getSpecHumidity()+ - * " RH="+layer.getRelativeHumidity()); } return soundLyList; } - */ - /** - * Return a list of data vertical levels for a specified time and model for - * grib or ncgrib data. - * - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param modelName - * the name of the model - * @return list of vertical levels - */ - public static List getModelLevels(String refTime, String validTime, - String pluginName, String modelName) { - - // Listvals = null; - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); - query.addDistinctParameter(GridConstants.LEVEL_ONE); - query.addQueryParam(GridConstants.PARAMETER_ABBREVIATION, "GH"); - query.addQueryParam(GridConstants.MASTER_LEVEL_NAME, "MB"); - - query.addQueryParam(GridConstants.DATASET_ID, modelName); - query.addQueryParam("dataTime.refTime", refTime); - query.addQueryParam("dataTime.validPeriod.start", validTime); - query.addOrder(GridConstants.LEVEL_ONE, false); - - try { - return (List) dao.queryByCriteria(query); - - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - } - - return null; - - } - - private static ISpatialObject spatialArea = null; - - /** - * Returns the indices of the model grid of the closest point to the - * specified latitude, longitude. - * - * @param lat - * latitude - * @param lon - * longitude - * @param level - * vertical level - * @param pluginName - * the name of the data table ('grib' or 'ncgrib') - * @param modelName - * the name of the model - * @return the point indices - */ - public static Point getLatLonIndices(double lat, double lon, - String refTime, String validTime, String level, String pluginName, - String modelName) { - // ISpatialObject spatialArea = null; - - Point pnt = null; - - if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { - CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); - DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); - - query.addQueryParam(GridConstants.PARAMETER_ABBREVIATION, "GH"); - query.addQueryParam(GridConstants.MASTER_LEVEL_NAME, "MB"); - query.addQueryParam(GridConstants.DATASET_ID, modelName); - query.addQueryParam("dataTime.refTime", refTime); - query.addQueryParam("dataTime.validPeriod.start", validTime); - query.addQueryParam(GridConstants.LEVEL_ONE, level); - query.addQueryParam(GridConstants.LEVEL_TWO, "-999999.0"); - - GridRecord rec; - try { - List recList = ((List) dao - .queryByCriteria(query)); - if (recList.size() == 0) { - return null; - } else { - rec = recList.get(0); - spatialArea = rec.getSpatialObject(); - } - } catch (DataAccessLayerException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - return null; - } - - } else - return null; - - GridGeometry2D geom = MapUtil.getGridGeometry(spatialArea); - - CoordinateReferenceSystem crs = geom.getCoordinateReferenceSystem(); - Coordinate coord = new Coordinate(lon, lat); - - try { - pnt = PointUtil.determineIndex(coord, crs, geom); - Integer nx = spatialArea.getNx(); - Integer ny = spatialArea.getNy(); - - if (pnt.x > nx || pnt.y > ny) { - return null; - } - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - return pnt; - - } - -} +package gov.noaa.nws.ncep.edex.uengine.tasks.profile; + +/** + * + * gov.noaa.nws.ncep.edex.uengine.tasks.profile.MdlSoundingQuery + * + * This java class performs the Grid model sounding data query functions. + * This code has been developed by the SIB for use in the AWIPS2 system. + * + *
+ * SOFTWARE HISTORY
+ * 
+ * Date         Ticket#    	Engineer    Description
+ * -------		------- 	-------- 	-----------
+ * 04/04/2011	301			Chin Chen	Initial coding
+ * 02/28/2012               Chin Chen   modify several sounding query algorithms for better performance
+ * 03/28/2012               Chin Chen   Add new API to support query multiple Points at one shoot and using
+ * 										dataStore.retrieveGroups()
+ * Oct 15, 2012 2473        bsteffen    Remove ncgrib
+ * 03/2014		1116		T. Lee		Added DpD
+ * 01/2015      DR#16959    Chin Chen   Added DpT support to fix DR 16959 NSHARP freezes when loading a sounding from 
+ *                                      HiRes-ARW/NMM models
+ * 02/03/2015   DR#17084    Chin Chen   Model soundings being interpolated below the surface for elevated sites                                     
+ * 
+ * + * @author Chin Chen + * @version 1.0 + */ +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingLayer; +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingModel; +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingProfile; +import gov.noaa.nws.ncep.edex.common.sounding.NcSoundingTimeLines; + +import java.awt.Point; +import java.util.ArrayList; +import java.util.List; + +import javax.measure.converter.UnitConverter; +import javax.measure.unit.NonSI; +import javax.measure.unit.SI; + +import org.geotools.coverage.grid.GridGeometry2D; +import org.geotools.geometry.GeneralDirectPosition; +import org.opengis.geometry.DirectPosition; +import org.opengis.referencing.FactoryException; +import org.opengis.referencing.crs.CoordinateReferenceSystem; +import org.opengis.referencing.operation.MathTransform; + +import com.raytheon.edex.uengine.tasks.query.TableQuery; +import com.raytheon.uf.common.dataplugin.PluginException; +import com.raytheon.uf.common.dataplugin.grid.GridConstants; +import com.raytheon.uf.common.dataplugin.grid.GridInfoRecord; +import com.raytheon.uf.common.dataplugin.grid.GridRecord; +import com.raytheon.uf.common.geospatial.ISpatialObject; +import com.raytheon.uf.common.geospatial.MapUtil; +import com.raytheon.uf.common.geospatial.PointUtil; +import com.raytheon.uf.common.topo.TopoQuery; +import com.raytheon.uf.edex.database.DataAccessLayerException; +import com.raytheon.uf.edex.database.dao.CoreDao; +import com.raytheon.uf.edex.database.dao.DaoConfig; +import com.raytheon.uf.edex.database.query.DatabaseQuery; +import com.vividsolutions.jts.geom.Coordinate; +import com.vividsolutions.jts.geom.CoordinateSequence; +import com.vividsolutions.jts.geom.Geometry; +import com.vividsolutions.jts.geom.GeometryFactory; +import com.vividsolutions.jts.geom.LinearRing; +import com.vividsolutions.jts.geom.Polygon; +import com.vividsolutions.jts.geom.impl.CoordinateArraySequence; + +//import org.opengis.geometry.Envelope; + +public class MdlSoundingQuery { + private static final String GRID_TBL_NAME = "grid"; + + private static String GRID_PARMS = "GH, uW, vW,T, DWPK, SPFH,OMEG, RH, DpD, DpT"; + + private enum GridParmNames { + GH, uW, vW, T, DWPK, SPFH, OMEG, RH, DpD, DpT + }; + + public static UnitConverter kelvinToCelsius = SI.KELVIN + .getConverterTo(SI.CELSIUS); + + private static final UnitConverter metersPerSecondToKnots = SI.METERS_PER_SECOND + .getConverterTo(NonSI.KNOT); + + // Note; we are using NCInventory now. So, this api is actually not used. + public static NcSoundingTimeLines getMdlSndTimeLine(String mdlType, + String currentDBTblName) { + NcSoundingTimeLines tl = new NcSoundingTimeLines(); + /* + * if(currentDBTblName.equals(NCGRIB_TBL_NAME)){ Object[] refTimeAry = + * null; String queryStr = new String("Select Distinct reftime FROM " + + * currentDBTblName + " where modelname='" + mdlType + + * "' ORDER BY reftime DESC"); + * + * CoreDao dao = new CoreDao(DaoConfig.forClass(NcgribRecord.class)); + * refTimeAry = (Object[]) dao.executeSQLQuery(queryStr); + * tl.setTimeLines(refTimeAry); }else + * if(currentDBTblName.equals(D2DGRIB_TBL_NAME)){ TableQuery query; try + * { query = new TableQuery("metadata", GridRecord.class.getName()); + * query.setDistinctField("dataTime.refTime"); + * query.addParameter(GridConstants.DATASET_ID, mdlType); + * query.setSortBy("dataTime.refTime", false); + * + * @SuppressWarnings("unchecked") List recList = + * (List) query.execute(); + * tl.setTimeLines(recList.toArray()); } catch (DataAccessLayerException + * e) { // TODO Auto-generated catch block e.printStackTrace(); } catch + * (Exception e) { // TODO Auto-generated catch block + * e.printStackTrace(); } + * + * } + */ + + // Chin: modified for Unified Grid DB + // Use the following SQL statement + // Select Distinct reftime FROM grid FULL JOIN grid_info ON + // grid.info_id=grid_info.id where grid_info.datasetid='gfs' ORDER BY + // reftime DESC + Object[] refTimeAry = null; + String queryStr = new String( + "Select Distinct reftime FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id where grid_info.datasetid='" + + mdlType + "' ORDER BY reftime DESC"); + + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + refTimeAry = dao.executeSQLQuery(queryStr); + tl.setTimeLines(refTimeAry); + + return tl; + } + + public static NcSoundingTimeLines getMdlSndRangeTimeLine(String mdlType, + String refTimeStr, String currentDBTblName) { + NcSoundingTimeLines tl = new NcSoundingTimeLines(); + /* + * if(currentDBTblName.equals(NCGRIB_TBL_NAME)){ Object[] refTimeAry = + * null; String queryStr = new String("Select Distinct rangestart FROM " + * + currentDBTblName + " where modelname='" + mdlType + "' AND " + + * "reftime='" + refTimeStr + ":00:00'" + " ORDER BY rangestart"); + * System.out.println("queryStr " + queryStr); + * + * CoreDao dao = new CoreDao(DaoConfig.forClass(SoundingSite.class)); + * refTimeAry = (Object[]) dao.executeSQLQuery(queryStr); + * tl.setTimeLines(refTimeAry); } else + * if(currentDBTblName.equals(D2DGRIB_TBL_NAME)){ TableQuery query; try + * { query = new TableQuery("metadata", GridRecord.class.getName()); + * query.setDistinctField("dataTime.validPeriod.start"); + * query.addParameter(GridConstants.DATASET_ID, mdlType); + * query.addParameter("dataTime.refTime", refTimeStr + ":00:00"); + * query.setSortBy("dataTime.validPeriod.start", true); + * + * @SuppressWarnings("unchecked") List recList = + * (List) query.execute(); + * tl.setTimeLines(recList.toArray()); } catch (DataAccessLayerException + * e) { // TODO Auto-generated catch block e.printStackTrace(); } catch + * (Exception e) { // TODO Auto-generated catch block + * e.printStackTrace(); } + */ + // Chin: modified for Unified Grid DB + // make sure data in DB is not just nHour data, as those data are not + // used by Nsharp. And when query to it, the returned will be + // null. We do not want to show such sounding time line to user. + // use this SQL query string for gfs as example. + /* + * Select Distinct rangestart FROM grid FULL JOIN grid_info ON + * grid.info_id=grid_info.id where grid.reftime = '2012-01-26 00:00:00' + * AND grid.rangestart = grid.rangeend AND + * grid_info.datasetid='mesoEta212' AND + * grid_info.parameter_abbreviation='T' order by rangestart + */ + Object[] soundingTimeAry = null; + List reSoundingTimeAry = new ArrayList(); + String queryStr = new String( + "Select Distinct rangestart FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id where grid.reftime = '" + + refTimeStr + + ":00:00' AND grid.rangestart = grid.rangeend AND grid_info.datasetid='" + + mdlType + + "' AND grid_info.parameter_abbreviation='T' order by rangestart"); + // System.out.println("queryStr " + queryStr); + + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + soundingTimeAry = (Object[]) dao.executeSQLQuery(queryStr); + for (int i = 0; i < soundingTimeAry.length; i++) { + /* + * Chin: make sure the time line has more than 5 T(temp) values at + * pressure (levelone) greater/equal than/to 100 hPa (mbar) use this + * SQL : Select count(rangestart) FROM (select rangestart FROM grid + * FULL JOIN grid_info ON grid.info_id=grid_info.id FULL JOIN level + * ON grid_info.level_id= level.id where grid.rangestart = + * '2012-01-26 03:00:00.0' AND grid.rangestart = grid.rangeend AND + * grid_info.datasetid='mesoEta212' AND + * grid_info.parameter_abbreviation='T' AND level.levelonevalue > + * 99) X HAVING count(X.rangestart) >5 + */ + String queryStr1 = new String( + "Select count(rangestart) FROM (select rangestart FROM grid FULL JOIN grid_info ON grid.info_id=grid_info.id FULL JOIN level ON grid_info.level_id= level.id where grid.rangestart = '" + + soundingTimeAry[i] + + "' AND grid.rangestart = grid.rangeend AND grid_info.datasetid='" + + mdlType + + "' AND grid_info.parameter_abbreviation='T' AND level.levelonevalue > 99) X HAVING count(X.rangestart) >2"); + Object[] countAry = null; + // System.out.println("queryStr1 " + queryStr1); + countAry = (Object[]) dao.executeSQLQuery(queryStr1); + java.math.BigInteger count = new java.math.BigInteger("0"); + if (countAry.length > 0) { + // System.out.println("rangestart =" + // +soundingTimeAry[i]+" number="+countAry[0]); + count = (java.math.BigInteger) countAry[0]; + } + // else{ + // System.out.println("rangestart =" + // +soundingTimeAry[i]+" return null"); + // } + if (count.intValue() > 2) { + Object timeLine = soundingTimeAry[i]; + reSoundingTimeAry.add(timeLine); + } + } + + tl.setTimeLines(reSoundingTimeAry.toArray()); + + // } + return tl; + } // public static NcSoundingProfile getMdlSndData(double lat, double lon, + + // String stn, long refTimeL, long validTimeL, String sndTypeStr, + // SndQueryKeyType queryType, String mdlName) { + // //*System.out.println("getPfcSndData input ref time = "+ + // refTimeL+" valid time is " + validTimeL); + // Calendar refTimeCal = Calendar.getInstance(TimeZone.getTimeZone("GMT")); + // refTimeCal.setTimeInMillis(refTimeL); + // Calendar validTimeCal = + // Calendar.getInstance(TimeZone.getTimeZone("GMT")); + // validTimeCal.setTimeInMillis(validTimeL); + // return getMdlSndData( lat, lon, refTimeCal, validTimeCal, "ncgrib", + // mdlName); + // } + + /** + * Returns a list of profile for location (lat,lon) array, time, and model + * for grib or ncgrib data. + * + * @param double[][] latLonArray, e.g. at nth element, lat=[n][0], + * lon=[n][1] + * @param refTimeCal + * data record reference time + * @param validTimeCal + * data record valid time + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param mdlName + * the name of the model + * @return the profile created @ 3/28/2012 + */ + public static List getMdlSndDataProfileList( + double[][] latLonArray, String refTime, String validTime, + String pluginName, String mdlName) { + double lat, lon; + // System.out.println("getMdlSndData lat=" + lat + " lon="+lon); + long t01 = System.currentTimeMillis(); + NcSoundingProfile pf = new NcSoundingProfile(); + // NcSoundingCube cube = new NcSoundingCube(); + List soundingProfileList = new ArrayList(); + List levels = getModelLevels(refTime, validTime, pluginName, mdlName); + if (levels.size() == 0) { + System.out.println("getModelLevels return 0; file=" + refTime + + " stime=" + validTime + " gribtype=" + pluginName + + " modeltype=" + mdlName); + return soundingProfileList; + } + // System.out.println("getModelLevels = "+ + // levels.size()+" levels, took "+ (System.currentTimeMillis()-t01) + + // " ms"); + + List points = new ArrayList(); + for (int k = 0; k < latLonArray.length; k++) { + lat = latLonArray[k][0]; + lon = latLonArray[k][1]; + Point pnt = getLatLonIndices(lat, lon, refTime, validTime, levels + .get(0).toString(), pluginName, mdlName); + if (pnt == null) { + System.out.println("getLatLonIndices return 0; lat=" + lat + + " lon=" + lon + " stime=" + validTime + " gribtype=" + + pluginName + " modeltype=" + mdlName); + } else { + points.add(pnt); + } + } + if (points.size() == 0) { + return soundingProfileList; + } + long t011 = System.currentTimeMillis(); + soundingProfileList = queryProfileListByPointGroup(points, refTime, + validTime, pluginName, mdlName, levels); + System.out.println("queryProfileListByPointGroup took " + + (System.currentTimeMillis() - t011) + " ms"); + + return soundingProfileList; + /* + * The floowing should be done in queryProfileListByPointGroup() + * //System.out.println("getModelSoundingLayerList= "+ layerList.size()+ + * " layers, took "+ (System.currentTimeMillis()-t012) + " ms"); + * //pf.setStationLatitude( lat); //pf.setStationLongitude( lon); + * //Float sfcPressure = getModelSfcPressure(pnt, refTime, validTime, // + * pluginName, mdlName); + * //System.out.println("getModelSfcPressure took "+ + * (System.currentTimeMillis()-t013) + " ms"); //if (sfcPressure == + * null) { // pf.setSfcPress(-9999.f); //} //else { // if + * (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) // + * pf.setSfcPress(sfcPressure/100F); // else // + * pf.setSfcPress(sfcPressure); //} + * //System.out.println("surface pressure ="+pf.getSfcPress()+ + * " lat= "+lat+ " lon="+lon); //calculate dew point if necessary long + * t014 = System.currentTimeMillis(); MergeSounding ms = new + * MergeSounding(); //ms.spfhToDewpoint(layerList); + * ms.rhToDewpoint(layerList); System.out.println("MergeSounding took "+ + * (System.currentTimeMillis()-t014) + " ms"); + * + * + * pf.setSoundingLyLst(layerList); + * + * + * soundingProfileList.add(pf); + * //cube.setSoundingProfileList(soundingProfileList); + * //cube.setRtnStatus(NcSoundingCube.QueryStatus.OK); long t02 = + * System.currentTimeMillis(); + * System.out.println("MDL cube retreival took " + (t02 - t01)); return + * pf; + */ + + } + + /** + * Returns a profile for a specified location (lat,lon), time, and model for + * grib or ncgrib data. + * + * @param lat + * location latitude + * @param lon + * location longitude + * @param refTimeCal + * data record reference time + * @param validTimeCal + * data record valid time + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param mdlName + * the name of the model + * @return the profile + * + * public static NcSoundingProfile getMdlSndData(double lat, double + * lon, String refTime, String validTime, String pluginName, String + * mdlName) { System.out.println("getMdlSndData lat=" + lat + + * " lon="+lon); long t01 = System.currentTimeMillis(); + * NcSoundingProfile pf = new NcSoundingProfile(); List levels = + * getModelLevels(refTime, validTime, pluginName, mdlName); if + * (levels.size() == 0) { + * System.out.println("getModelLevels return 0; file=" + refTime+ + * " stime="+validTime + " gribtype="+ pluginName + + * " modeltype="+mdlName); + * pf.setRtnStatus(NcSoundingCube.QueryStatus.FAILED); return pf; } + * System.out.println("getModelLevels = "+ + * levels.size()+" levels, took "+ (System.currentTimeMillis()-t01) + * + " ms"); long t011 = System.currentTimeMillis(); Point pnt = + * getLatLonIndices(lat, lon, refTime, validTime, + * levels.get(0).toString(), pluginName, mdlName); if (pnt == null) + * { System.out.println("getLatLonIndices return 0; lat=" + lat + + * " lon="+lon+" stime="+validTime + " gribtype="+ pluginName + + * " modeltype="+mdlName); + * + * pf.setRtnStatus(NcSoundingCube.QueryStatus.LOCATION_NOT_FOUND); + * return pf; + * + * } System.out.println("getLatLonIndices pntX=" + pnt.getX()+ + * " pntY=" + pnt.getY()+ " took "+ + * (System.currentTimeMillis()-t011) + " ms"); long t012 = + * System.currentTimeMillis(); List layerList = + * getModelSoundingLayerList(pnt, refTime, validTime, pluginName, + * mdlName, levels); if (layerList.size() == 0) { + * System.out.println("getModelSoundingLayerList return 0; lat=" + + * lat + " lon="+lon+" stime="+validTime + " gribtype="+ pluginName + * + " modeltype="+mdlName); + * + * + * pf.setRtnStatus(NcSoundingCube.QueryStatus.FAILED); return pf; } + * + * System.out.println("getModelSoundingLayerList= "+ + * layerList.size()+ " layers, took "+ + * (System.currentTimeMillis()-t012) + " ms"); + * + * pf.setStationLatitude( lat); pf.setStationLongitude( lon); Float + * sfcPressure = getModelSfcPressure(pnt, refTime, validTime, + * pluginName, mdlName); + * //System.out.println("getModelSfcPressure took "+ + * (System.currentTimeMillis()-t013) + " ms"); if (sfcPressure == + * null) { pf.setSfcPress(-9999.f); } else { if + * (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) + * pf.setSfcPress(sfcPressure/100F); else + * pf.setSfcPress(sfcPressure); } + * //System.out.println("surface pressure ="+pf.getSfcPress()+ + * " lat= "+lat+ " lon="+lon); //calculate dew point if necessary + * long t014 = System.currentTimeMillis(); MergeSounding ms = new + * MergeSounding(); //ms.spfhToDewpoint(layerList); + * ms.rhToDewpoint(layerList); + * System.out.println("MergeSounding took "+ + * (System.currentTimeMillis()-t014) + " ms"); + * + * + * pf.setSoundingLyLst(layerList); + * + * long t02 = System.currentTimeMillis(); + * System.out.println("MDL cube retreival took " + (t02 - t01)); + * return pf; + * + * } + */ + + public static NcSoundingModel getMdls(String pluginName) { + NcSoundingModel mdls = new NcSoundingModel(); + Object[] mdlName = null; + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + CoreDao dao = new CoreDao(DaoConfig.forClass(GridInfoRecord.class)); + String queryStr = new String( + "Select Distinct modelname FROM grib_models ORDER BY modelname"); + mdlName = (Object[]) dao.executeSQLQuery(queryStr); + } + if (mdlName != null && mdlName.length > 0) { + List mdlList = new ArrayList(); + for (Object mn : mdlName) { + mdlList.add((String) mn); + } + mdls.setMdlList(mdlList); + } + return mdls; + } + + public static boolean isPointWithinGridGeometry(double lat, double lon, + String refTime, String validTime, String pluginName, + String modelName) { + + ISpatialObject spatialArea = null; + MathTransform crsFromLatLon = null; + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); + + query.setMaxResults(new Integer(1)); + query.addQueryParam(GridConstants.DATASET_ID, modelName); + query.addQueryParam("dataTime.refTime", refTime); + query.addQueryParam("dataTime.validPeriod.start", validTime); + + try { + List recList = ((List) dao + .queryByCriteria(query)); + if (recList.size() == 0) { + return false; + } else { + GridRecord rec = recList.get(0); + spatialArea = rec.getSpatialObject(); + } + + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } + + try { + crsFromLatLon = MapUtil + .getTransformFromLatLon(spatialArea.getCrs()); + } catch (FactoryException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + + DirectPosition lowerCorner = MapUtil.getGridGeometry(spatialArea) + .getEnvelope().getLowerCorner(); + DirectPosition upperCorner = MapUtil.getGridGeometry(spatialArea) + .getEnvelope().getUpperCorner(); + + GeometryFactory gf = new GeometryFactory(); + + Coordinate p1 = new Coordinate(lowerCorner.getOrdinate(0), + lowerCorner.getOrdinate(1)); + Coordinate p2 = new Coordinate(lowerCorner.getOrdinate(0), + upperCorner.getOrdinate(1)); + Coordinate p3 = new Coordinate(upperCorner.getOrdinate(0), + upperCorner.getOrdinate(1)); + Coordinate p4 = new Coordinate(upperCorner.getOrdinate(0), + lowerCorner.getOrdinate(1)); + + LinearRing lr = gf.createLinearRing(new Coordinate[] { p1, p2, p3, p4, + p1 }); + + Polygon gridGeometry = gf.createPolygon(lr, null); + + DirectPosition ll = new GeneralDirectPosition(MapUtil.LATLON_PROJECTION); + + Coordinate coord = new Coordinate(lon, lat); + ll.setOrdinate(0, coord.x); + ll.setOrdinate(1, coord.y); + // DirectPosition crs = new GeneralDirectPosition(spatialArea.getCrs()); + // try { + // crsFromLatLon.transform(ll, crs); + // } catch (MismatchedDimensionException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } catch (TransformException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + // + // Coordinate newC = new Coordinate(crs.getOrdinate(0), + // crs.getOrdinate(1)); + Coordinate newC = new Coordinate(ll.getOrdinate(0), ll.getOrdinate(1)); + + com.vividsolutions.jts.geom.Point p = gf.createPoint(newC); + + return gridGeometry.contains(p); + + } + + public static boolean isPointWithinGridGeometry2(double lat, double lon, + String refTime, String validTime, String pluginName, + String modelName) { + + ISpatialObject spatialArea = null; + MathTransform crsFromLatLon = null; + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); + + query.setMaxResults(new Integer(1)); + query.addQueryParam(GridConstants.DATASET_ID, modelName); + query.addQueryParam("dataTime.refTime", refTime); + query.addQueryParam("dataTime.validPeriod.start", validTime); + + try { + List recList = ((List) dao + .queryByCriteria(query)); + if (recList.size() == 0) { + return false; + } else { + GridRecord rec = recList.get(0); + spatialArea = rec.getSpatialObject(); + } + + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return false; + } + } + + Geometry g = spatialArea.getGeometry(); + + GeometryFactory geometryFactory = new GeometryFactory(); + CoordinateSequence sequence = new CoordinateArraySequence( + g.getCoordinates()); + + Coordinate[] oldCoords = sequence.toCoordinateArray(); + Coordinate[] newCoords = new Coordinate[oldCoords.length]; + /* + * adjust longitude for global grids whose lon span goes from 0 to 360 + * and the asked lon is negative. + */ + for (Coordinate c : oldCoords) { + double x = c.x; + double y = c.y; + double z = c.z; + if (x >= 180.0 && x <= 360.0 && lon < 0.0) { + lon = lon + 360.0; + break; + } + } + Coordinate coord = new Coordinate(lon, lat); + + LinearRing ring = new LinearRing(sequence, geometryFactory); + Polygon gridGeometry = new Polygon(ring, null, geometryFactory); + com.vividsolutions.jts.geom.Point p = geometryFactory + .createPoint(coord); + + return gridGeometry.contains(p); + + } + + /** + * Returns the value of surface layer for a specified location, time, and + * model for grib or ncgrib data. + * + * @param pnt + * location + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param modelName + * the name of the model + * @return surface pressure + * + * DR17084 + */ + @SuppressWarnings("unchecked") + public static NcSoundingLayer getModelSfcLayer(Point pnt, String refTime, + String validTime, String pluginName, String modelName, Coordinate coordinate) { + + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + NcSoundingLayer soundingLy = new NcSoundingLayer(); + TableQuery query; + try { + query = new TableQuery("metadata", GridRecord.class.getName()); + query.addParameter(GridConstants.LEVEL_ONE, "0.0"); + query.addParameter(GridConstants.LEVEL_TWO, "-999999.0"); + query.addParameter(GridConstants.MASTER_LEVEL_NAME, "SFC"); + query.addList(GridConstants.PARAMETER_ABBREVIATION, "P, GH"); + query.addParameter(GridConstants.DATASET_ID, modelName); + query.addParameter("dataTime.refTime", refTime); + query.addParameter("dataTime.validPeriod.start", validTime); + List recList = (List) query.execute(); + boolean presureAvailable=false, heightAvailable=false; + if (recList!=null && recList.size() > 0) { + for(GridRecord rec:recList ){ + PointIn pointIn = new PointIn(pluginName, rec, pnt.x, pnt.y); + try { + + float fdata = pointIn.getPointData(); + String parm = rec.getParameter().getAbbreviation(); + if(parm.equals("P")){ + soundingLy.setPressure(fdata/100); + presureAvailable = true; + } if(parm.equals("GH")){ + soundingLy.setGeoHeight(fdata); + heightAvailable = true; + } + //System.out.println("prm="+rec.getParameter().getAbbreviation()+" value="+fdata); + + } catch (PluginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + recList.clear(); + } + query = new TableQuery("metadata", GridRecord.class.getName()); + query.addParameter(GridConstants.LEVEL_ONE, "2.0"); + query.addParameter(GridConstants.LEVEL_TWO, "-999999.0"); + query.addParameter(GridConstants.MASTER_LEVEL_NAME, "FHAG"); + query.addList(GridConstants.PARAMETER_ABBREVIATION, "T, RH"); + query.addParameter(GridConstants.DATASET_ID, modelName); + query.addParameter("dataTime.refTime", refTime); + query.addParameter("dataTime.validPeriod.start", validTime); + recList = (List) query.execute(); + if (recList!=null && recList.size() > 0) { + for(GridRecord rec:recList ){ + PointIn pointIn = new PointIn(pluginName, rec, pnt.x, pnt.y); + try { + + float fdata = pointIn.getPointData(); + String parm = rec.getParameter().getAbbreviation(); + if(parm.equals("T")){ + soundingLy.setTemperature((float) kelvinToCelsius + .convert(fdata)); + } if(parm.equals("RH")){ + soundingLy.setRelativeHumidity(fdata); + } + //System.out.println("prm="+rec.getParameter().getAbbreviation()+" value="+fdata); + + } catch (PluginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + recList.clear(); + } + + query = new TableQuery("metadata", GridRecord.class.getName()); + query.addParameter(GridConstants.LEVEL_ONE, "10.0"); + query.addParameter(GridConstants.LEVEL_TWO, "-999999.0"); + query.addParameter(GridConstants.MASTER_LEVEL_NAME, "FHAG"); + query.addList(GridConstants.PARAMETER_ABBREVIATION, "vW, uW"); + query.addParameter(GridConstants.DATASET_ID, modelName); + query.addParameter("dataTime.refTime", refTime); + query.addParameter("dataTime.validPeriod.start", validTime); + recList = (List) query.execute(); + if (recList!=null && recList.size() > 0) { + for(GridRecord rec:recList ){ + PointIn pointIn = new PointIn(pluginName, rec, pnt.x, pnt.y); + try { + + float fdata = pointIn.getPointData(); + String parm = rec.getParameter().getAbbreviation(); + if(parm.equals("vW")){ + soundingLy.setWindV((float) metersPerSecondToKnots + .convert(fdata)); + } if(parm.equals("uW")){ + soundingLy.setWindU((float) metersPerSecondToKnots + .convert(fdata)); + } + //System.out.println("prm="+rec.getParameter().getAbbreviation()+" value="+fdata); + + } catch (PluginException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + } + if (presureAvailable==false || heightAvailable==false) { + float surfaceElevation = NcSoundingProfile.MISSING; + TopoQuery topoQuery = TopoQuery.getInstance(); + if (topoQuery != null) { + //System.out.println("Nsharp coordinate.x="+coordinate.x); + surfaceElevation = (float) topoQuery + .getHeight(coordinate); + if(surfaceElevation >=0) + soundingLy.setGeoHeight(surfaceElevation); + else { + if (presureAvailable==false) + //no pressure and no height, no hope to continue. + return null; + } + } + else { + if (presureAvailable==false) + //no pressure and no height, no hope to continue. + return null; + } + } + return soundingLy; + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } catch (Exception e1) { + // TODO Auto-generated catch block + e1.printStackTrace(); + } + } + return null; + + } + + /** + * Returns a list of NcSoundingProfile for a group of Point with specific + * ref and range time, and model for grib or ncgrib data. + * + * @param pnt + * location + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param modelName + * the name of the model + * @param levels + * list of vertical levels + * @return list of NcSoundingLayer objects + * + * Created @ 3/28/2012 + */ + + private static List queryProfileListByPointGroup( + List points, String refTime, String validTime, + String pluginName, String modelName, List levels) { + + List soundingProfileList = new ArrayList(); + List fdataArrayList = new ArrayList(); + // long t01 = System.currentTimeMillis(); + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + List recList = new ArrayList(); + ; + TableQuery query; + try { + query = new TableQuery("metadata", GridRecord.class.getName()); + query.addParameter(GridConstants.MASTER_LEVEL_NAME, "MB"); + query.addParameter(GridConstants.DATASET_ID, modelName); + query.addList(GridConstants.PARAMETER_ABBREVIATION, GRID_PARMS); + query.addParameter("dataTime.refTime", refTime); + query.addParameter("dataTime.validPeriod.start", validTime); + query.setSortBy(GridConstants.LEVEL_ONE, false); + recList = (List) query.execute(); + if (recList.size() != 0) { + PointIn pointIn = new PointIn(pluginName, recList.get(0)); + fdataArrayList = pointIn.getHDF5GroupDataPoints( + recList.toArray(), points); + } + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + int index = 0; + GridGeometry2D geom = MapUtil.getGridGeometry(spatialArea); + CoordinateReferenceSystem crs = geom.getCoordinateReferenceSystem(); + Coordinate coord = new Coordinate(45, 45); + + for (float[] fdataArray : fdataArrayList) { + // one fdataArray is for one Point or say one profile + NcSoundingProfile pf = new NcSoundingProfile(); + List soundLyList = new ArrayList(); + Point pnt = points.get(index); + Object[] recArray = recList.toArray(); + for (Object level : levels) { + NcSoundingLayer soundingLy = new NcSoundingLayer(); + double pressure = (Double) level; + soundingLy.setPressure((float) pressure); + for (int i = 0; i < recArray.length; i++) { + GridRecord rec1 = (GridRecord) recArray[i]; + float fdata = fdataArray[i]; + if (rec1.getLevel().getLevelonevalue() == pressure) { + String prm = rec1.getParameter().getAbbreviation(); + //System.out.println("prm="+prm+" value="+fdata); + switch (GridParmNames.valueOf(prm)) { + case GH: + soundingLy.setGeoHeight(fdata); + break; + case uW: + // HDF5 data in unit of m/s, convert to Knots + // 4/12/2012 + soundingLy + .setWindU((float) metersPerSecondToKnots + .convert(fdata)); + break; + case vW: + // HDF5 data in unit of m/s, convert to Knots + // 4/12/2012 + soundingLy + .setWindV((float) metersPerSecondToKnots + .convert(fdata)); + break; + case T: + soundingLy + .setTemperature((float) kelvinToCelsius + .convert(fdata)); + break; + case DWPK: + soundingLy.setDewpoint((float) kelvinToCelsius + .convert(fdata)); + break; + case OMEG: + soundingLy.setOmega(fdata); + break; + case RH: + soundingLy.setRelativeHumidity(fdata); + break; + case DpD: + soundingLy.setDpd(fdata); + break; + case DpT: + soundingLy.setDewpoint((float) kelvinToCelsius + .convert(fdata)); + break; + case SPFH: + soundingLy.setSpecHumidity(fdata); + break; + default: + break; + } + } + } + soundLyList.add(soundingLy); + } + try { + coord = PointUtil.determineLatLon(pnt, crs, geom); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + //System.out.println(" point coord.y="+coord.y+ " coord.x="+ + // coord.x); + pf.setStationLatitude(coord.y); + pf.setStationLongitude(coord.x); + // DR17084 + NcSoundingLayer sfcLayer = getModelSfcLayer(pnt, refTime, + validTime,pluginName, modelName, coord); + if (sfcLayer != null) { + if(sfcLayer.getPressure()== NcSoundingLayer.MISSING && + sfcLayer.getGeoHeight()!= NcSoundingLayer.MISSING){ + //surface layer does not have pressure, but surface height is available + //see if we can interpolate surface pressure from upper and lower layer pressure + for (int i = 0; i < soundLyList.size(); i++) { + if (soundLyList.get(i).getGeoHeight() > sfcLayer.getGeoHeight()) { + if (i > 0) { + float p1 = soundLyList.get(i - 1).getPressure(); + float p2 = soundLyList.get(i).getPressure(); + float h1 = soundLyList.get(i - 1).getGeoHeight(); + float h2 = soundLyList.get(i).getGeoHeight(); + float h = sfcLayer.getGeoHeight(); + float p = p1 + (h - h1) * (p1 - p2) / (h1 - h2); + sfcLayer.setPressure(p); + } + break; + } + } + } + if(sfcLayer.getPressure()!= NcSoundingLayer.MISSING){ + // cut sounding layer under ground, i.e. below surface layer + for(int i= soundLyList.size()-1; i>=0 ; i--){ + NcSoundingLayer ly = soundLyList.get(i); + if(ly.getPressure() >= sfcLayer.getPressure()){ + soundLyList.remove(i); + } + } + soundLyList.add(0, sfcLayer); + } + pf.setSfcPress(sfcLayer.getPressure()); + pf.setStationElevation(sfcLayer.getGeoHeight()); + } + //System.out.println("surface pressure ="+pf.getSfcPress()); + //end DR17084 + // calculate dew point if necessary + MergeSounding ms = new MergeSounding(); + // ms.spfhToDewpoint(layerList); + ms.rhToDewpoint(soundLyList); + ms.dpdToDewpoint(soundLyList); + pf.setSoundingLyLst(soundLyList); + soundingProfileList.add(pf); + index++; + } + } + return soundingProfileList; + } + + /** + * Returns a list of NcSoundingLayer for a specified location, time, and + * model for grib or ncgrib data. + * + * @param pnt + * location + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param modelName + * the name of the model + * @param levels + * list of vertical levels + * @return list of NcSoundingLayer objects + * + * + * private static List + * getModelSoundingLayerList(Point pnt, String refTime, String + * validTime, String pluginName, String modelName, List levels) { + * List soundLyList = new + * ArrayList(); + * + * //long t01 = System.currentTimeMillis(); if + * (pluginName.equalsIgnoreCase(NCGRIB_TBL_NAME)) { + * + * TableQuery query; try { query = new TableQuery("metadata", + * NcgribRecord.class.getName()); query.addParameter("vcord", + * "PRES"); query.addParameter("modelName", modelName); + * query.addList("parm",NC_PARMS);//parmList.toString()); // + * query.addParameter("dataTime.refTime", refTime); + * query.addParameter("dataTime.validPeriod.start", validTime); + * //query.addParameter("glevel1", level.toString()); + * query.setSortBy("glevel1", false); + * + * + * List recList = (List) + * query.execute(); + * System.out.println("Ncgrib group query0 result size ="+ + * recList.size()); + * + * if (recList.size() != 0) { + * + * PointIn pointIn = new PointIn(pluginName, recList.get(0), pnt.x, + * pnt.y); //Chin note: // We query all levels (pressure) and all + * parameters (at that level) at once. // The return array + * (fdataArray) are listed in the same order as query array + * (recList.toArray()) //However, returned array does not tell you + * which parameter itself is. //Therefore, we have to use + * information in query array to find out returned value's type + * (which parameter it is) // Further, we have to sort and store + * returned values to NcSoundingLayer based on its level (pressure) + * // Parameters in same level should be stored in one same + * NcSoundingLayer float[] fdataArray = + * pointIn.getHDF5GroupDataPoint(recList.toArray()); Object[] + * recArray = recList.toArray(); for (Object level : levels){ + * NcSoundingLayer soundingLy = new NcSoundingLayer(); int pressure= + * (Integer)level; soundingLy.setPressure( pressure); + * + * for (int i=0; i < recArray.length; i++) { NcgribRecord rec1 = + * (NcgribRecord)recArray[i]; float fdata = fdataArray[i]; + * if(rec1.getGlevel1() == pressure){ String prm = rec1.getParm(); + * //System.out.println("point.x="+ pnt.x + + * " .y="+pnt.y+"pressure="+rec1 // .getGlevel1()+ " Parm="+prm ); + * //long t01 = System.currentTimeMillis(); switch + * (NcParmNames.valueOf(prm)) { case HGHT: + * soundingLy.setGeoHeight(fdata); break; case UREL: // HDF5 data in + * unit of Knots, no conversion needed soundingLy.setWindU(fdata); + * break; case VREL: // HDF5 data in unit of Knots, no conversion + * needed soundingLy.setWindV(fdata); break; case TMPK: + * soundingLy.setTemperature((float) kelvinToCelsius + * .convert(fdata)); break; case DWPK: + * soundingLy.setDewpoint((float) kelvinToCelsius .convert(fdata)); + * break; case SPFH: soundingLy.setSpecHumidity(fdata); break; case + * OMEG: soundingLy.setOmega(fdata); break; case RELH: + * soundingLy.setRelativeHumidity(fdata); break; } } } + * soundLyList.add(soundingLy); } } + * + * } catch (DataAccessLayerException e) { // TODO Auto-generated + * catch block e.printStackTrace(); } catch (Exception e) { // TODO + * Auto-generated catch block e.printStackTrace(); } + * //System.out.println("getModelSoundingLayerList:total level = "+ + * totalLevel + " total records= "+totalRecords ); + * + * } else if (pluginName.equalsIgnoreCase(D2DGRIB_TBL_NAME)) { try { + * TableQuery query = new TableQuery("metadata", + * GribRecord.class.getName()); + * //query.addParameter("modelInfo.level.levelonevalue", // + * level.toString()); + * //query.addParameter("modelInfo.level.leveltwovalue", // + * "-999999.0"); + * query.addParameter("modelInfo.level.masterLevel.name", "MB"); + * query.addParameter("modelInfo.modelName", modelName); + * query.addList("modelInfo.parameterAbbreviation", D2D_PARMS); + * query.addParameter("dataTime.refTime", refTime); + * query.addParameter("dataTime.validPeriod.start", validTime); + * query.setSortBy("modelInfo.level.levelonevalue", false); + * //System.out.println("level = "+ level.toString()); + * + * List recList = (List) query.execute(); + * System.out.println("Grib group query0 result size ="+ + * recList.size()); + * + * if (recList.size() > 0) { PointIn pointIn = new + * PointIn(pluginName, recList.get(0), pnt.x, pnt.y); float[] + * fdataArray = pointIn.getHDF5GroupDataPoint(recList.toArray()); + * Object[] recArray = recList.toArray(); for (Object level : + * levels){ NcSoundingLayer soundingLy = new NcSoundingLayer(); + * double pressure= (Double)level; soundingLy.setPressure( + * (float)pressure); + * + * for (int i=0; i < recArray.length; i++) { GribRecord rec1 = + * (GribRecord)recArray[i]; float fdata = fdataArray[i]; + * if(rec1.getModelInfo().getLevelOneValue() == pressure){ String + * prm = rec1.getModelInfo().getParameterAbbreviation(); + * //System.out.println("point.x="+ pnt.x + + * " .y="+pnt.y+"pressure="+pressure+ " Parm="+prm ); //long t01 = + * System.currentTimeMillis(); switch (D2DParmNames.valueOf(prm)) { + * case GH: soundingLy.setGeoHeight(fdata); break; case uW: // HDF5 + * data in unit of Knots, no conversion needed + * soundingLy.setWindU(fdata); break; case vW: // HDF5 data in unit + * of Knots, no conversion needed soundingLy.setWindV(fdata); break; + * case T: soundingLy.setTemperature((float) kelvinToCelsius + * .convert(fdata)); break; case DWPK: + * soundingLy.setDewpoint((float) kelvinToCelsius .convert(fdata)); + * break; case OMEG: soundingLy.setOmega(fdata); break; case RH: + * soundingLy.setRelativeHumidity(fdata); break; } } } + * soundLyList.add(soundingLy); } } } catch + * (DataAccessLayerException e) { // TODO Auto-generated catch block + * e.printStackTrace(); } catch (Exception e) { // TODO + * Auto-generated catch block e.printStackTrace(); } } + * + * //long t02 = System.currentTimeMillis(); + * //System.out.println("MDL profile retreival took " + (t02 - + * t01)); + * + * for(NcSoundingLayer layer: soundLyList){ + * System.out.println("pre="+ layer.getPressure()+ + * " h="+layer.getGeoHeight()+ " T="+layer.getTemperature()+" D="+ + * layer.getDewpoint()+ " WS="+layer.getWindSpeed()+ + * " WD="+layer.getWindDirection() + " SH="+layer.getSpecHumidity()+ + * " RH="+layer.getRelativeHumidity()); } return soundLyList; } + */ + /** + * Return a list of data vertical levels for a specified time and model for + * grib or ncgrib data. + * + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param modelName + * the name of the model + * @return list of vertical levels + */ + public static List getModelLevels(String refTime, String validTime, + String pluginName, String modelName) { + + // Listvals = null; + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); + query.addDistinctParameter(GridConstants.LEVEL_ONE); + query.addQueryParam(GridConstants.PARAMETER_ABBREVIATION, "GH"); + query.addQueryParam(GridConstants.MASTER_LEVEL_NAME, "MB"); + + query.addQueryParam(GridConstants.DATASET_ID, modelName); + query.addQueryParam("dataTime.refTime", refTime); + query.addQueryParam("dataTime.validPeriod.start", validTime); + query.addOrder(GridConstants.LEVEL_ONE, false); + + try { + return (List) dao.queryByCriteria(query); + + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + } + + return null; + + } + + private static ISpatialObject spatialArea = null; + + /** + * Returns the indices of the model grid of the closest point to the + * specified latitude, longitude. + * + * @param lat + * latitude + * @param lon + * longitude + * @param level + * vertical level + * @param pluginName + * the name of the data table ('grib' or 'ncgrib') + * @param modelName + * the name of the model + * @return the point indices + */ + public static Point getLatLonIndices(double lat, double lon, + String refTime, String validTime, String level, String pluginName, + String modelName) { + // ISpatialObject spatialArea = null; + + Point pnt = null; + + if (pluginName.equalsIgnoreCase(GRID_TBL_NAME)) { + CoreDao dao = new CoreDao(DaoConfig.forClass(GridRecord.class)); + DatabaseQuery query = new DatabaseQuery(GridRecord.class.getName()); + + query.addQueryParam(GridConstants.PARAMETER_ABBREVIATION, "GH"); + query.addQueryParam(GridConstants.MASTER_LEVEL_NAME, "MB"); + query.addQueryParam(GridConstants.DATASET_ID, modelName); + query.addQueryParam("dataTime.refTime", refTime); + query.addQueryParam("dataTime.validPeriod.start", validTime); + query.addQueryParam(GridConstants.LEVEL_ONE, level); + query.addQueryParam(GridConstants.LEVEL_TWO, "-999999.0"); + + GridRecord rec; + try { + List recList = ((List) dao + .queryByCriteria(query)); + if (recList.size() == 0) { + return null; + } else { + rec = recList.get(0); + spatialArea = rec.getSpatialObject(); + } + } catch (DataAccessLayerException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + return null; + } + + } else + return null; + + GridGeometry2D geom = MapUtil.getGridGeometry(spatialArea); + + CoordinateReferenceSystem crs = geom.getCoordinateReferenceSystem(); + Coordinate coord = new Coordinate(lon, lat); + + try { + pnt = PointUtil.determineIndex(coord, crs, geom); + Integer nx = spatialArea.getNx(); + Integer ny = spatialArea.getNy(); + + if (pnt.x > nx || pnt.y > ny) { + return null; + } + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + return pnt; + + } + +} diff --git a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java index b4f5606f6b..4eab444db9 100644 --- a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java +++ b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java @@ -17,6 +17,7 @@ package gov.noaa.nws.ncep.ui.nsharp.display.rsc; * 01/27/2015 DR#17006, * Task#5929 Chin Chen NSHARP freezes when loading a sounding from MDCRS products * in Volume Browser + * 02/03/2015 DR#17084 Chin Chen Model soundings being interpolated below the surface for elevated sites * * * @@ -38,6 +39,7 @@ import gov.noaa.nws.ncep.ui.nsharp.background.NsharpSkewTPaneBackground; import gov.noaa.nws.ncep.ui.nsharp.background.NsharpTurbulencePaneBackground; import gov.noaa.nws.ncep.ui.nsharp.display.NsharpSkewTPaneDescriptor; import gov.noaa.nws.ncep.ui.nsharp.natives.NsharpNative; +import gov.noaa.nws.ncep.ui.nsharp.natives.NsharpNative.NsharpLibrary; import gov.noaa.nws.ncep.ui.nsharp.natives.NsharpNative.NsharpLibrary._lplvalues; import gov.noaa.nws.ncep.ui.nsharp.natives.NsharpNative.NsharpLibrary._parcel; import gov.noaa.nws.ncep.ui.nsharp.natives.NsharpNativeConstants; @@ -1256,32 +1258,40 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { double p_mb = c.y; double temp = c.x; float htFt, htM, relh = -1; - String curStrFormat, curStrFormat1; + String curStrFormat, curStrFormat1, htMStr, htFtStr; String curStr, curStr1;// , curStr2,curStr3; VerticalAlignment vAli; HorizontalAlignment hAli; // curStr3 = rscHandler.getPickedStnInfoStr()+"\n"; - - htM = nsharpNative.nsharpLib.agl(nsharpNative.nsharpLib + //Chin, DR17084 + if(soundingLys.get(0).getGeoHeight() <0){ + htMStr="M"; + htFtStr="M"; + } + else { + htM = nsharpNative.nsharpLib.agl(nsharpNative.nsharpLib .ihght((float) p_mb)); - htFt = nsharpNative.nsharpLib.mtof(htM); + htFt = nsharpNative.nsharpLib.mtof(htM); + htMStr = Integer.toString(Math.round(htM)); + htFtStr = Integer.toString(Math.round(htFt)); + } if (nsharpNative.nsharpLib.itemp((float) p_mb) > -9998.0 && nsharpNative.nsharpLib.idwpt((float) p_mb) > -9998.0) { FloatByReference parm = new FloatByReference(0); relh = nsharpNative.nsharpLib.relh((float) p_mb, parm); - curStrFormat = "%4.0f/%.0fkt %4.0fmb %5.0fft/%.0fm agl %2.0f%%\n"; + curStrFormat = "%4.0f/%.0fkt %4.0fmb %sft/%sm agl %2.0f%%\n"; curStr = String.format(curStrFormat, nsharpNative.nsharpLib.iwdir((float) p_mb), - nsharpNative.nsharpLib.iwspd((float) p_mb), p_mb, htFt, - htM, relh); + nsharpNative.nsharpLib.iwspd((float) p_mb), p_mb, htFtStr, + htMStr, relh); } else { - curStrFormat = "%4.0f/%.0fkt %4.0fmb %5.0fft/%.0fm agl\n"; + curStrFormat = "%4.0f/%.0fkt %4.0fmb %sft/%sm agl\n"; curStr = String .format(curStrFormat, nsharpNative.nsharpLib.iwdir((float) p_mb), nsharpNative.nsharpLib.iwspd((float) p_mb), p_mb, - htFt, htM); + htFtStr, htMStr); } /* * curStrFormat1 = "%s/%s %4.1f/%4.1f%cC %4.0f/%.0f kt\n"; curStr1 = @@ -2695,11 +2705,11 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { // Chin 08/04/2014, fixed surface height plotting bug // also fixed to draw height mraker based on AGL (i.e. above surface // level) - int sfcIndex = nsharpNative.nsharpLib.sfc(); + int sfcIndex = 0; ////DR#17084 nnsharpNative.nsharpLib.sfc(); int sfcAsl = 0; - if (sfcIndex >= 0 - && sfcIndex < soundingLys.size() - && soundingLys.get(sfcIndex).getGeoHeight() != NsharpNativeConstants.NSHARP_NATIVE_INVALID_DATA) { + if (//DR#17084 sfcIndex >= 0 + //DR#17084 && sfcIndex < soundingLys.size() + soundingLys.get(sfcIndex).getGeoHeight() != NsharpNativeConstants.NSHARP_NATIVE_INVALID_DATA) { double y = world.mapY(NsharpWxMath.getSkewTXY( soundingLys.get(sfcIndex).getPressure(), 0).y); try { From 9c9bb841caa35b27af24ebb11649ccb6e8d65c88 Mon Sep 17 00:00:00 2001 From: "Shawn.Hooper" Date: Fri, 30 Jan 2015 10:10:54 -0500 Subject: [PATCH 12/34] ASM #17100 - Revert "VLab Issue #4563 - AWIPS2_DR_17371 Changes made in MPE editor not available in D-2D; fixes #4563" This reverts commit cbc7d2813fff630eba8967086f0610738cef6c9e [formerly bf5bb1bacf1543e518ff5c5cdb847c8424f70739] [formerly bfc550b378d565ed9ef1c5354d9c5cef01802c0f [formerly 5189ae1c3ce12320045c431d9552036ea9272cef]]. Change-Id: Ie8d46f14aab90286a5562e6b79cd7dfabceb33c0 Former-commit-id: fa9e3791cb6d0f835de4d725ca4c62c6962cddd6 [formerly 0ed82706fda1158292e6f789cceea378f5879e37] Former-commit-id: 0ac88acf058b8e1c8a8a51dbc9f43d6338f71229 --- .../utility/edex_static/base/grib/models/gribModels_RFC-9.xml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_RFC-9.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_RFC-9.xml index 26a2e937c2..9186a97899 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_RFC-9.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_RFC-9.xml @@ -23,7 +23,7 @@ - MPE-Local + MPE-Mosaic
9
0 304 @@ -50,7 +50,6 @@
- From 84100aa1c5c87073f95f0440d50a9a9da1117b34 Mon Sep 17 00:00:00 2001 From: Chin Chen Date: Thu, 5 Feb 2015 10:27:54 -0500 Subject: [PATCH 13/34] VLab Issue #5618 - Comp(Src) button not functioning properly in NSHARP display Change-Id: Id78b05a091a67eed244ebcb0e5a181fd18dfe9b4 Former-commit-id: 8cd20947b98177679b9aa2f57af2f8d4d25c49fb [formerly 10c4645f7532d03fb647ec45a600baee8b7de1a4] Former-commit-id: 866738321555b3ba27ee2cb9249a108afc8b5534 --- .../display/rsc/NsharpResourceHandler.java | 74 ++++++++++++------- .../display/rsc/NsharpSkewTPaneResource.java | 23 +++--- .../rsc/NsharpTimeStnPaneResource.java | 31 +++++++- .../ui/nsharp/view/NsharpPaletteWindow.java | 18 +++++ 4 files changed, 108 insertions(+), 38 deletions(-) diff --git a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpResourceHandler.java b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpResourceHandler.java index b100409d46..89a3af3a37 100644 --- a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpResourceHandler.java +++ b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpResourceHandler.java @@ -18,6 +18,8 @@ * 01/27/2015 DR#17006, * Task#5929 Chin Chen NSHARP freezes when loading a sounding from MDCRS products * in Volume Browser + * 02/03/2015 DR#17079 Chin Chen Soundings listed out of order if frames go into new month + * 02/05/2015 DR16888 CHin Chen merge 12/11/2014 fixes at version 14.2.2 and check in again to 14.3.1 * * * @author Chin Chen @@ -581,12 +583,14 @@ public class NsharpResourceHandler { * The following code is to create a list of stns within the range * of user defined radius (minimum distance) to "current" station * and also has data loaded with same time line as "current" time - * line. Note that we have two time line formats, MM.DDVxxx(day) - * and MM.DD(day). A same time line is compared by MM.DD - * only. All qualified stations, including current station, found - * will be listed and used for SND comparison. + * line. Note that we have two time line formats, YYMMDD/HHVxxx(day) + * and YYMMDD/HH(day) saved in NsharpSoundingElementStateProperty. + * A same time line is compared by "YYMMDD/HH" only. All qualified + * stations, including current station, found will be listed and used + * for SND comparison. */ - String TIME_COMPARE_STRING = "MM.DD"; + //DR#17079 + String TIME_COMPARE_STRING = "YYMMDD/HH"; compSndSelectedElemList.clear(); // CompSndSelectedElem curStnTimeIndexCouple = new // CompSndSelectedElem(currentStnElementListIndex,currentTimeElementListIndex,currentSndElementListIndex); @@ -1074,13 +1078,14 @@ public class NsharpResourceHandler { private class NsharpOperationElementComparator implements Comparator { - @Override + + @Override public int compare(NsharpOperationElement o1, NsharpOperationElement o2) { String s1tok1 = "";// , s1tok2=""; String s2tok1 = "";// , s2tok2=""; - StringTokenizer st1 = new StringTokenizer( - o1.getElementDescription()); + String o1Desc = o1.getElementDescription(); + StringTokenizer st1 = new StringTokenizer(o1Desc); int tkCount1 = st1.countTokens(); // System.out.println("ElementComparatorTimeLine o1="+o1.elementDescription+"c1 = "+tkCount1); if (tkCount1 >= 1) { @@ -1090,8 +1095,8 @@ public class NsharpResourceHandler { } // System.out.println("t1="+s1tok1+" t2="+s2tok1); - StringTokenizer st2 = new StringTokenizer( - o2.getElementDescription()); + String o2Desc = o2.getElementDescription(); + StringTokenizer st2 = new StringTokenizer(o2Desc); int tkCount2 = st2.countTokens(); // System.out.println("ElementComparatorTimeLine o2="+o2.elementDescription+"c2 = "+tkCount2); if (tkCount2 >= 1) { @@ -1890,21 +1895,22 @@ public class NsharpResourceHandler { Map> soundMap, NsharpStationInfo stnInfo, boolean fromArchive) { - // // testing code // stnInfo.setStnId("KUKI"); -// Set keysettest = new HashSet(soundMap.keySet()); -// for (String key : keysettest) { -// List sndLy = soundMap.remove(key); // String - // // newkey= -// String newkey =key.replace("NCUAIR", "gpduair"); // String newkey = - // String newkey= key.replace("NAMS", "SSS"); -// String newkey = key.replace("130925/17(Wed)V017", "131001/00(Thu)V000"); - // soundMap.put(newkey, sndLy); -// } + /* + * testing code : this is helpful code. do not remove. + * + Set keysettest = new HashSet(soundMap.keySet()); + for (String key : keysettest) { + List sndLy = soundMap.remove(key); + String newkey= key.replace("NAMS", "SSS"); + //String newkey = key.replace("130925", "150102"); + soundMap.put(newkey, sndLy); + } // // stnInfo.setSndType(stnInfo.getSndType().replace("NCUAIR", // // // "gpduair")); // stnInfo.setSndType(stnInfo.getSndType().replace( // // "NAMS","SSS")); - // // - + * + * END testing code + */ if (stnInfo.getStnId() != null && stnInfo.getStnId().indexOf(" ") >= 0) { // take care stnId with SPACE case. String stnId = stnInfo.getStnId(); @@ -2022,10 +2028,18 @@ public class NsharpResourceHandler { } /* - * As of 2014 April 9, current time description string is + * Chin: 04092014, current time description string is * defined as "YYMMDD/HH(DOW)" or "YYMMDD/HH(DOW)Vxxx". Convert * them to "DD.HH(DOW)" or "DD.HHVxxx(DOW)" for GUI display. */ + /* + * Chin: 02032015 + * "DR17079: Soundings listed out of order if frames go into new month" + * To fix DR17079, keep time line format as yymmdd/hh(day)Vxxx to be saved + * in NsharpSoundingElementStateProperty. + * We will convert display string while drawing. + * This chunk code is to convert time line format from yymmdd/hh(day)Vxxx to dd.hh(day)Vxxx + * timeLine = timeLine.substring(4); // get rid of YYMM if (timeLine.contains("V")) { String[] s1Str = timeLine.split("V"); // split @@ -2042,6 +2056,9 @@ public class NsharpResourceHandler { // "DD/HHVxxx(DOW)" } timeLine = timeLine.replace("/", "."); // replace "/" with "." + * + * end DR17079 + */ } // recreate stnId_timeLine_sndType stnId_timeLine_sndType = stnId + " " + timeLine + " " + sndType; @@ -2657,7 +2674,10 @@ public class NsharpResourceHandler { break; } - + if (compareSndIsOn) { + handleUserPickNewTimeLine(currentTimeElementListIndex) ; + return; + } curTimeLinePage = currentTimeElementListIndex / numTimeLinePerPage + 1; setCurSndProfileProp(); @@ -2794,7 +2814,10 @@ public class NsharpResourceHandler { // we should get out of here break; } else if (compareSndIsOn) { - boolean found = false; + handleUserPickNewTimeLine(targetIndex) ; + return; + /* DR16888 02052015 + * boolean found = false; if (currentStnElementListIndex >= 0 && currentSndElementListIndex >= 0 && stnTimeSndTable @@ -2839,6 +2862,7 @@ public class NsharpResourceHandler { // not // we should get out of here break; + */ } else { break; } diff --git a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java index 4eab444db9..776df87dd7 100644 --- a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java +++ b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpSkewTPaneResource.java @@ -18,7 +18,8 @@ package gov.noaa.nws.ncep.ui.nsharp.display.rsc; * Task#5929 Chin Chen NSHARP freezes when loading a sounding from MDCRS products * in Volume Browser * 02/03/2015 DR#17084 Chin Chen Model soundings being interpolated below the surface for elevated sites - * + * 02/05/2015 DR16888 Chin Chen fixed issue that "Comp(Src) button not functioning properly in NSHARP display" + * merged 12/11/2014 fixes at version 14.2.2 and check in again to 14.3.1 * * * @author Chin Chen @@ -1876,7 +1877,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { boolean overlayIsOn = rscHandler.isOverlayIsOn(); if (graphConfigProperty != null) { if (graphConfigProperty.isTemp() == true && !compareStnIsOn - && !compareTmIsOn) { + && !compareTmIsOn && !compareSndIsOn) { if (editGraphOn) plotPressureTempEditPoints(target, world, NsharpConstants.color_red, TEMP_TYPE, @@ -1884,7 +1885,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } // dew point curve if (graphConfigProperty.isDewp() == true && !compareStnIsOn - && !compareTmIsOn) { + && !compareTmIsOn && !compareSndIsOn) { if (editGraphOn) plotPressureTempEditPoints(target, world, NsharpConstants.color_green, DEWPOINT_TYPE, @@ -1892,7 +1893,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } // plot wet bulb trace if (graphConfigProperty.isWetBulb() == true && rscHandler.isGoodData() //#5929 - && !compareStnIsOn && !compareTmIsOn) { + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { NsharpLineProperty lp = linePropertyMap .get(NsharpConstants.lineNameArray[NsharpConstants.LINE_WETBULB]); target.drawWireframeShape(wetBulbTraceRscShape, @@ -1901,7 +1902,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } // plot virtual temperature trace if (graphConfigProperty.isVTemp() == true && rscHandler.isGoodData() //#5929 - && !compareStnIsOn && !compareTmIsOn) { + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { NsharpLineProperty lp = linePropertyMap .get(NsharpConstants.lineNameArray[NsharpConstants.LINE_VIRTUAL_TEMP]); target.drawWireframeShape(vtempTraceCurveRscShape, @@ -1910,7 +1911,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } // virtual temperature parcel trace curve if (graphConfigProperty.isParcelTv() == true && rscHandler.isGoodData() //#5929 - && !compareStnIsOn && !compareTmIsOn + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn && !overlayIsOn) { if (soundingLys.size() > 0) { NsharpLineProperty lp = linePropertyMap @@ -1922,7 +1923,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } if (graphConfigProperty.isDcape() == true && rscHandler.isGoodData() //#5929 - && dacpeTraceRscShape != null && !compareStnIsOn + && dacpeTraceRscShape != null && !compareStnIsOn && !compareSndIsOn && !compareTmIsOn && !overlayIsOn) { if (soundingLys.size() > 0) { NsharpLineProperty lp = linePropertyMap @@ -1934,7 +1935,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } } if (graphConfigProperty.isEffLayer() == true && rscHandler.isGoodData() //#5929 - && !compareStnIsOn && !compareTmIsOn) { + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { // draw effective layer lines // drawEffectiveLayerLines(target); target.drawWireframeShape(effectiveLayerLineShape, @@ -1943,7 +1944,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } // cloud if (graphConfigProperty.isCloud() == true && rscHandler.isGoodData() //#5929 - && !compareStnIsOn && !compareTmIsOn) { + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { if (cloudFMShape != null) target.drawShadedShape(cloudFMShape, 1f); if (cloudFMLabelShape != null) @@ -1954,7 +1955,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { target.drawShadedShape(cloudCEShape, 1f); } if (graphConfigProperty.isOmega() == true - && !compareStnIsOn && !compareTmIsOn) { + && !compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { if (NsharpLoadDialog.getAccess() != null && (NsharpLoadDialog.getAccess() .getActiveLoadSoundingType() == NsharpLoadDialog.MODEL_SND || NsharpLoadDialog @@ -1966,7 +1967,7 @@ public class NsharpSkewTPaneResource extends NsharpAbstractPaneResource { } } else { // by default, draw everything - if (!compareStnIsOn && !compareTmIsOn) { + if (!compareStnIsOn && !compareTmIsOn && !compareSndIsOn) { if (editGraphOn) plotPressureTempEditPoints(target, world, NsharpConstants.color_red, TEMP_TYPE, diff --git a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpTimeStnPaneResource.java b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpTimeStnPaneResource.java index 5b83ac4583..234ca6a96c 100644 --- a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpTimeStnPaneResource.java +++ b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/display/rsc/NsharpTimeStnPaneResource.java @@ -11,6 +11,7 @@ package gov.noaa.nws.ncep.ui.nsharp.display.rsc; * Date Ticket# Engineer Description * ------- ------- -------- ----------- * 04/23/2012 229 Chin Chen Initial coding + * 02/03/2015 DR#17079 Chin Chen Soundings listed out of order if frames go into new month * * * @@ -244,6 +245,32 @@ public class NsharpTimeStnPaneResource extends NsharpAbstractPaneResource { target.clearClippingPlane(); } + /* + * Chin 02032015 - DR17079 + * currently time line description is YYMMDD/HHVxxx(day) or + * YYMMDD/HH(day) saved in NsharpSoundingElementStateProperty + * convert it to DD.HH for displaying + */ + private String convertTimeLineForDisplay(String tl){ + String timeLine = tl; + timeLine = timeLine.substring(4); // get rid of YYMM + if (timeLine.contains("V")) { + String[] s1Str = timeLine.split("V"); // split + // DD/HH(DOW)Vxxx to + // "DD/HH(DOW)" and + // "xxx" + String[] s2Str = s1Str[0].split("\\("); // split + // "DD/HH(DOW)" to + // "DD/HH" and + // "DOW)" + timeLine = s2Str[0] + "V" + s1Str[1] + "(" + s2Str[1]; // put + // together + // to + // "DD/HHVxxx(DOW)" + } + timeLine = timeLine.replace("/", "."); // replace "/" with "." + return timeLine; + } @SuppressWarnings("deprecation") private void drawNsharpTimelinBox(IGraphicsTarget target, Rectangle rect) @@ -344,8 +371,8 @@ public class NsharpTimeStnPaneResource extends NsharpAbstractPaneResource { x = x + xGap; RGB tmLnColor = rscHandler.getElementColorMap().get(sta.name()); String tmDesStr = elm.getElementDescription(); - // d2dlite - convert timeDesStr for GUI display - + // DR17079: convert timeDesStr for GUI display + tmDesStr = convertTimeLineForDisplay(tmDesStr); double tmX = x; if (compareTmIsOn diff --git a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/view/NsharpPaletteWindow.java b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/view/NsharpPaletteWindow.java index dd31940cce..4b94faccfe 100644 --- a/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/view/NsharpPaletteWindow.java +++ b/ncep/gov.noaa.nws.ncep.ui.nsharp/src/gov/noaa/nws/ncep/ui/nsharp/view/NsharpPaletteWindow.java @@ -17,6 +17,7 @@ * 01/13/2014 Chin Chen TTR829- when interpolation, edit graph is allowed * 01/22/2014 Chin Chen DR17003 issue: NSHARP sounding display throws errors when swapping into main pane when show text is turned on. * 10/20/2014 Chin Chen DR16864, D2D does not use unload button. Check to make sure not D2D instance before access unload button. + * 02/04/2015 DR16888 Chin Chen do not allow swap between Skewt and hodo when comparison is on, check in with DR17079 * * * @author Chin Chen @@ -185,10 +186,13 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, graphModeBtnSkew.setEnabled(true); graphModeBtnIcing.setEnabled(true); graphModeBtnTurb.setEnabled(true); + if(graphModeBtnHodo!=null) + graphModeBtnHodo.setEnabled(true); if (currentGraphMode == NsharpConstants.GRAPH_SKEWT) { graphModeBtnSkew.setBackground(colorBlue); graphModeBtnTurb.setBackground(colorGrey); graphModeBtnIcing.setBackground(colorGrey); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setBackground(colorGrey); graphEditBtn.setEnabled(true); dataEditBtn.setEnabled(true); compareTmBtn.setEnabled(true); @@ -219,6 +223,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, compareStnBtn.setEnabled(false); interpBtn.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); graphModeBtnTurb.setEnabled(false); if (!imD2d) unloadBtn.setEnabled(false); // FixMark:nearByStnCompSnd @@ -232,6 +237,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, interpBtn.setEnabled(false); graphModeBtnIcing.setEnabled(false); graphModeBtnTurb.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); if (!imD2d) unloadBtn.setEnabled(false); // FixMark:nearByStnCompSnd } else if (compareSndIsOn) { @@ -243,6 +249,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, overlayBtn.setEnabled(false); interpBtn.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); graphModeBtnTurb.setEnabled(false); if (!imD2d) unloadBtn.setEnabled(false); // FixMark:nearByStnCompSnd @@ -255,6 +262,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, overlayBtn.setEnabled(false); interpBtn.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); graphModeBtnTurb.setEnabled(false); if (!imD2d) unloadBtn.setEnabled(false); // FixMark:nearByStnCompSnd @@ -268,6 +276,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, interpBtn.setEnabled(false); graphModeBtnIcing.setEnabled(false); graphModeBtnTurb.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); if (!imD2d) unloadBtn.setEnabled(false); // FixMark:nearByStnCompSnd } @@ -707,6 +716,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(true); graphModeBtnIcing.setEnabled(true); graphModeBtnTurb.setEnabled(true); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(true); graphEditBtn.setText(EDIT_GRAPH_OFF); currentGraphMode = NsharpConstants.GRAPH_SKEWT; NsharpEditor editor = NsharpEditor.getActiveNsharpEditor(); @@ -948,6 +958,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(false); graphModeBtnTurb.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(false); interpBtn.setEnabled(false); cfgBtn.setEnabled(false); if (!imD2d) @@ -962,6 +973,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(true); graphModeBtnTurb.setEnabled(true); graphModeBtnIcing.setEnabled(true); + if(graphModeBtnHodo!=null) graphModeBtnHodo.setEnabled(true); interpBtn.setEnabled(true); cfgBtn.setEnabled(true); if (!imD2d) @@ -1006,6 +1018,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(false); graphModeBtnTurb.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(false); interpBtn.setEnabled(false); cfgBtn.setEnabled(false); if (!imD2d) @@ -1020,6 +1033,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(true); graphModeBtnTurb.setEnabled(true); graphModeBtnIcing.setEnabled(true); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(true); interpBtn.setEnabled(true); cfgBtn.setEnabled(true); if (!imD2d) @@ -1066,6 +1080,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(false); graphModeBtnTurb.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(false); interpBtn.setEnabled(false); cfgBtn.setEnabled(false); if (!imD2d) @@ -1080,6 +1095,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(true); graphModeBtnTurb.setEnabled(true); graphModeBtnIcing.setEnabled(true); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(true); interpBtn.setEnabled(true); cfgBtn.setEnabled(true); if (!imD2d) @@ -1124,6 +1140,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(false); graphModeBtnTurb.setEnabled(false); graphModeBtnIcing.setEnabled(false); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(false); interpBtn.setEnabled(false); cfgBtn.setEnabled(false); if (!imD2d) @@ -1138,6 +1155,7 @@ public class NsharpPaletteWindow extends ViewPart implements SelectionListener, dataEditBtn.setEnabled(true); graphModeBtnTurb.setEnabled(true); graphModeBtnIcing.setEnabled(true); + if(graphModeBtnHodo!=null)graphModeBtnHodo.setEnabled(true); interpBtn.setEnabled(true); cfgBtn.setEnabled(true); if (!imD2d) From 1d02b073a4bdeb4730f943ae8bfca020e76b67ed Mon Sep 17 00:00:00 2001 From: "steve.naples" Date: Thu, 5 Feb 2015 20:21:33 +0000 Subject: [PATCH 14/34] ASM #17101 MPE: Bad data not being written to file. Change-Id: Id1524abf8ae7379f5d8aac31b4d9cfff0469a577 Former-commit-id: d52da7d4fa839fcedb8921f47bf2768c92af31d4 [formerly fe3d6ffc320ea338d755c6fb19aa4f00c74c4283] Former-commit-id: c292ad2b0857cf6b15db5524f338c599a738da07 --- .../com/raytheon/viz/mpe/ui/actions/GroupEditCalls.java | 9 +++------ .../src/com/raytheon/viz/mpe/util/BadValues.java | 6 +++--- .../src/com/raytheon/viz/mpe/util/GetBadSnotel.java | 4 ++-- .../com/raytheon/viz/mpe/util/ReadPrecipStationList.java | 3 +-- 4 files changed, 9 insertions(+), 13 deletions(-) diff --git a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/GroupEditCalls.java b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/GroupEditCalls.java index 530293fbb4..a9d0229ebc 100644 --- a/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/GroupEditCalls.java +++ b/cave/com.raytheon.viz.mpe.ui/src/com/raytheon/viz/mpe/ui/actions/GroupEditCalls.java @@ -43,6 +43,7 @@ import com.raytheon.viz.mpe.util.ReadTemperatureStationList; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Mar 31, 2009 snaples Initial creation + * Feb 5, 2015 17101 snaples Updated max_stations to use size of dqc.precip_stations. * * * @@ -72,13 +73,9 @@ public class GroupEditCalls { int pcpn_time = dqc.pcpn_time; - ReadPrecipStationList rp = new ReadPrecipStationList(); + int max_stations = dqc.precip_stations.size(); - ReadTemperatureStationList rt = new ReadTemperatureStationList(); - - int max_stations = rp.getNumPstations(); - - int max_tstations = rt.getNumTstations(); + int max_tstations = dqc.temperature_stations.size(); public void apply_group() diff --git a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/BadValues.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/BadValues.java index 382178e1ff..986316baab 100644 --- a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/BadValues.java +++ b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/BadValues.java @@ -41,6 +41,7 @@ import com.raytheon.viz.mpe.util.DailyQcUtils.Station; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Mar 9, 2009 snaples Initial creation + * Feb 5, 2015 17101 snaples Fixed issue with writing zero length bad values file. * * * @@ -51,8 +52,7 @@ import com.raytheon.viz.mpe.util.DailyQcUtils.Station; public class BadValues { BufferedReader in = null; DailyQcUtils dqc = DailyQcUtils.getInstance(); - ReadPrecipStationList rp = new ReadPrecipStationList(); - private int max_stations = rp.getNumPstations(); + private int max_stations = dqc.precip_stations.size(); public void read_bad_values(String precd, int m) { @@ -194,7 +194,7 @@ public class BadValues { // ier=sprintf(ibuf,"%s %s %d %f\n",bad_values[i].hb5,bad_values[i].parm, // bad_values[i].quart,bad_values[i].fvalue); - ibuf = String.format("%s %s %d %f", bad_values[i].hb5, + ibuf = String.format("%s %s %d %4.2f", bad_values[i].hb5, bad_values[i].parm, bad_values[i].quart, bad_values[i].fvalue); out.write(ibuf); diff --git a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/GetBadSnotel.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/GetBadSnotel.java index ebc695574d..ebc3af1584 100644 --- a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/GetBadSnotel.java +++ b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/GetBadSnotel.java @@ -41,6 +41,7 @@ import com.raytheon.viz.mpe.util.DailyQcUtils.Station; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Mar 11, 2009 snaples Initial creation + * Feb 5, 2015 17101 snaples Updated max_stations to use size of dqc.precip_stations. * * * @@ -68,8 +69,7 @@ public class GetBadSnotel { /* Retrieve the number of days to QC data for. */ num_qc_days = dqc.qcDays; - ReadPrecipStationList pl = new ReadPrecipStationList(); - int max_stations = pl.getNumPstations(); + int max_stations = dqc.precip_stations.size(); // Pdata[] pdata = DailyQcUtils.pdata; for (j = 0; j < num_qc_days; j++) { diff --git a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/ReadPrecipStationList.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/ReadPrecipStationList.java index e20445a4f8..5fe2cc096f 100644 --- a/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/ReadPrecipStationList.java +++ b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/ReadPrecipStationList.java @@ -73,8 +73,7 @@ public class ReadPrecipStationList { e.printStackTrace(); } - ReadPrecipStationList.max_pstations = dqc.precip_stations - .size(); + max_pstations = dqc.precip_stations.size(); return dqc.precip_stations; From 8a5e418304c86ca4ad8577d998f8ae29aceac341 Mon Sep 17 00:00:00 2001 From: "Rici.Yu" Date: Thu, 5 Feb 2015 15:22:49 -0500 Subject: [PATCH 15/34] ASM #16942 - GFE: VTEC Active Tables not getting purged Change-Id: Ib71d667d8f59ab1fa3c669653d2bc8636bd7901c Former-commit-id: 14bb4d4d5925e91f71883e2fcdb0257a5adee4bf [formerly 6fbdb136e3968e197c38907a20ba898d9532e39e] Former-commit-id: 48dcae90644401fe8eab8663a9599f62b481f009 --- .../com/raytheon/uf/edex/activetable/ActiveTable.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java index 6a92c24049..459ca8251b 100644 --- a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java +++ b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java @@ -91,6 +91,11 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery; * Jun 17, 2014 3296 randerso Cached PythonScript. Moved active table * backup and purging to a separate thread. * Added performance logging + * Feb 05, 2015 16942 ryu Fix update of records for sites other than + * the home site and its neighbors. + * Pass issuance site id to getActiveTable() + * in updateActiveTable() so records will + * be updated correctly. * * * @@ -285,11 +290,13 @@ public class ActiveTable { mode = ActiveTableMode.OPERATIONAL; } + String issueSiteId = newRecords.get(0).getOfficeid(); + IPerformanceStatusHandler perfStat = PerformanceStatus .getHandler("ActiveTable"); ITimer timer = TimeUtil.getTimer(); timer.start(); - List activeTable = getActiveTable(siteId, mode); + List activeTable = getActiveTable(issueSiteId, mode); timer.stop(); perfStat.logDuration("getActiveTable", timer.getElapsedTime()); From 8389938a6a2337a84491bd1a90842f5a26a685ff Mon Sep 17 00:00:00 2001 From: "Kiran.Shrestha" Date: Mon, 9 Feb 2015 20:03:16 +0000 Subject: [PATCH 16/34] ASM #17085 - lifted index issue reporting in units of Kelvin but labeled as Celsius Change-Id: I004f3bab00aa82130ea21d888a1307f76ec58849 Former-commit-id: 89b18b15d7067e7bb73f3a660535b3758dc9c584 [formerly 391a2576ade26ad7031e037db5e9695915212cd5] Former-commit-id: a4f63f4147dbcd429b066202c1e2478544a1ab67 --- .../base/styleRules/d2dContourStyleRules.xml | 10 +++++----- .../base/styleRules/d2dGraphStyleRules.xml | 2 +- .../base/styleRules/gridImageryStyleRules.xml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dContourStyleRules.xml b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dContourStyleRules.xml index 6ccb42d172..746c85eaa0 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dContourStyleRules.xml +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dContourStyleRules.xml @@ -1201,7 +1201,7 @@ C | 1 | 0 | 4 | | |..|8000F0FF| | 0 | 2 PLI - K + C 2 @@ -1212,7 +1212,7 @@ C | 1 | 0 | 4 | | |..|8000F0FF| | 0 | 2 SLI - K + C NoPlane 2 @@ -1229,7 +1229,7 @@ C | 1 | 0 | 4 | | |..|8000F0FF|,900| 0 | 2 Laps - K + C NoPlane 2 @@ -1244,7 +1244,7 @@ C | 1 | 0 | 4 | | |..|8000F0FF| | 0 | 2 BLI - K + C 2 @@ -2771,7 +2771,7 @@ C | 1 | 0 | 4 | | |..|8000F0FF|,,,80 | 0 | 2 80 - K + C NoPlane 2 diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dGraphStyleRules.xml b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dGraphStyleRules.xml index 55d705073b..0946e24814 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dGraphStyleRules.xml +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/d2dGraphStyleRules.xml @@ -347,7 +347,7 @@ shWlt - K + C diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/gridImageryStyleRules.xml b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/gridImageryStyleRules.xml index efc4cdabd6..a482e59ab9 100644 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/gridImageryStyleRules.xml +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.grid/utility/common_static/base/styleRules/gridImageryStyleRules.xml @@ -1780,7 +1780,7 @@ PLI - K + C -15 15 @@ -1800,7 +1800,7 @@ BLI - K + C -15 15 From e1e2b963852f6d60f9066971b1a473b15bf42d2e Mon Sep 17 00:00:00 2001 From: David Friedman Date: Tue, 10 Feb 2015 23:15:09 +0000 Subject: [PATCH 17/34] ASM #17112 - Radar watchdog alarms continuously and on dial radars Change-Id: Ie2e638720cf06fe54e23a8cd5291ec90d9a310c2 Former-commit-id: c728581a8af6f34ccc162ec4de2acb3e904f88b8 [formerly 52e8f3e02eca979152a8eff84fa1e776a962fa70] Former-commit-id: 790837e01070c97afdc23b0a7ad246adb079aa9a --- .../com/raytheon/rcm/server/RadarServer.java | 2 +- .../raytheon/rcm/server/RadarWatchdog.java | 198 +++++++++++------- .../rcm/server/RadarWatchdogListener.java | 41 +--- 3 files changed, 130 insertions(+), 111 deletions(-) diff --git a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarServer.java b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarServer.java index b8c6dc8186..36a96004b9 100755 --- a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarServer.java +++ b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarServer.java @@ -124,7 +124,7 @@ public class RadarServer implements RadarEventListener { addListener(new DedicatedRadarActivator(this)); addListener(new RequestScheduler(this)); addListener(new RadarServerAvailable(this)); - addListener(new RadarWatchdogListener(configuration)); + addListener(new RadarWatchdogListener(this)); } public void addListener(RadarEventListener l) { diff --git a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdog.java b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdog.java index 081098dd22..c1761000b5 100644 --- a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdog.java +++ b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdog.java @@ -29,7 +29,8 @@ import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import com.raytheon.rcm.config.Configuration; +import com.raytheon.rcm.config.RadarConfig; +import com.raytheon.rcm.server.StatusManager.RadarStatus; /** * @@ -43,6 +44,8 @@ import com.raytheon.rcm.config.Configuration; * Date Ticket# Engineer Description * ------------- ---------- ----------- -------------------------- * May 12, 2014 DR 16319 dhuffman Initial creation. + * Feb 10, 2015 DR 17112 D. Friedman Only alarm on dedicated radars and + * once per detected failure. * * * @@ -56,18 +59,16 @@ public class RadarWatchdog extends Thread { protected static class GsmItem { protected String radarID; - protected int vcp; - protected long time; - protected long alarmTime; - protected long nextAlarmTime; + protected int trackedVcp; + protected int currentVcp; + protected boolean failed; protected GsmItem() { } } protected static class RadarItem { - protected String radarID; - protected String mnemonic; + protected boolean isNew; protected long time; protected long messageTime; @@ -75,7 +76,10 @@ public class RadarWatchdog extends Thread { } } - private long startTime = 0; + protected static class WatchedRadar { + + } + private long shortestWait = 0; private static final long fudgeTime = 30; @@ -84,13 +88,13 @@ public class RadarWatchdog extends Thread { private static Map mapDuration = new ConcurrentHashMap(); private static List mapMnemonicProducts = new ArrayList(); - protected Configuration configuration; + protected RadarServer radarServer; private static String configFileName = "radarWatchdog.txt"; - protected RadarWatchdog(Configuration conf) { + protected RadarWatchdog(RadarServer radarServer) { + super("Watchdog"); setDaemon(true); - startTime = System.currentTimeMillis(); - configuration = conf; + this.radarServer = radarServer; loadConfigFile(configFileName); @@ -102,112 +106,145 @@ public class RadarWatchdog extends Thread { } } - public GsmItem getGSMItem(final String radarID) { - return mapGSM.get(radarID); - } - - public void putGSMItem(GsmItem gi) { - if (gi != null) { - mapGSM.put(gi.radarID, gi); - } - } - - public RadarItem getRadarItem(final String Mnemonic, final String radarID) { - Map mapRadar = mapMnemonic.get(Mnemonic); - if (mapRadar != null) - return mapRadar.get(radarID); - return null; - } - - public void putRadarItem(RadarItem ri) { - if (ri != null) { - Map mapRadar = mapMnemonic.get(ri.mnemonic); - if (mapRadar != null) { - mapRadar.put(ri.radarID, ri); + public synchronized void notifyGsm(String radarID, int vcp) { + GsmItem item = mapGSM.get(radarID); + if (item != null) { + item.currentVcp = vcp; + notifyWatchdog(); + } else { + if (isTrackedRadar(radarID)) { + item = new GsmItem(); + item.radarID = radarID; + item.currentVcp = vcp; + mapGSM.put(radarID, item); + notifyWatchdog(); } } } + protected boolean isTrackedRadar(String radarID) { + RadarConfig rc = radarServer.getConfiguration().getConfigForRadar(radarID); + return rc != null && rc.isDedicated(); + } + + public synchronized void notifyRadarItem(String radarID, String mnemonic, long messageTime, long time) { + if (! isTrackedRadar(radarID)) + return; + + RadarItem ri = getRadarItem(mnemonic, radarID); + if (ri != null) { + ri.isNew = false; + ri.messageTime = messageTime; + ri.time = time; + } + } + + private RadarItem getRadarItem(final String mnemonic, final String radarID) { + Map mapRadar = mapMnemonic.get(mnemonic); + if (mapRadar != null) { + RadarItem ri = mapRadar.get(radarID); + if (ri == null) { + ri = new RadarItem(); + ri.isNew = true; + mapRadar.put(radarID, ri); + } + return ri; + } + return null; + } + @Override public void run() { - long currentTime = 0; shortestWait = 0; while (true) { try { synchronized (semifore) { semifore.wait(shortestWait < 800 ? 800 : shortestWait); - currentTime = System.currentTimeMillis(); } } catch (InterruptedException e) { e.printStackTrace(); } - patrol(currentTime); + patrol(System.currentTimeMillis()); } } - private void patrol(long currentTime) { + private synchronized void patrol(long currentTime) { long duration = 0; - long adjustedTime = currentTime - fudgeTime; - shortestWait = 0; + long earliestCheckTime = 0; Iterator git = mapGSM.values().iterator(); while (git.hasNext()) { GsmItem gi = git.next(); - if (mapDuration.get(gi.vcp) != null) { - duration = mapDuration.get(gi.vcp) * 1000; - Iterator mnem = mapMnemonicProducts.iterator(); - while (mnem.hasNext()) { - String mn = mnem.next(); - Map mapRadar = mapMnemonic.get(mn); - if (mapRadar == null) - continue; - RadarItem ri = mapRadar.get(gi.radarID); + if (! isTrackedRadar(gi.radarID)) { + git.remove(); + continue; + } - if (ri == null) { - if (duration + startTime < adjustedTime - && gi.alarmTime != startTime) { - alert(duration, gi, mn); - gi.alarmTime = startTime; - gi.nextAlarmTime = startTime + duration; - } - - if (shortestWait < 1 || duration < shortestWait) - shortestWait = duration; - } + /* There will be an alarm when the radar connection goes down, so + * do not do any additional alarming. + */ + RadarStatus rs = radarServer.getStatusManager().getRadarStatus(gi.radarID); + if (rs != null && rs.getCurrentGSM() == null) { + gi.trackedVcp = 0; + continue; + } + if (gi.currentVcp != gi.trackedVcp) { + for (String mn : mapMnemonicProducts) { + RadarItem ri = getRadarItem(mn, gi.radarID); if (ri != null) { + ri.isNew = true; + } + } + gi.trackedVcp = gi.currentVcp; + } - if (ri.time + duration < adjustedTime) { - if (ri.time <= gi.alarmTime - && gi.nextAlarmTime < currentTime) { - alert(duration, gi, ri.mnemonic); - gi.alarmTime = ri.time; - gi.nextAlarmTime = currentTime + duration; - } - if (gi.nextAlarmTime < currentTime) - gi.alarmTime = ri.time; - } + if (mapDuration.get(gi.trackedVcp) != null) { + boolean allOk = true; + duration = (mapDuration.get(gi.trackedVcp) + fudgeTime) * 1000; - if ((duration + ri.time) - adjustedTime < shortestWait - && 1 <= (duration + ri.time) - adjustedTime) - shortestWait = (duration + ri.time) - adjustedTime; - if (shortestWait < 1) - shortestWait = duration; + for (String mn : mapMnemonicProducts) { + RadarItem ri = getRadarItem(mn, gi.radarID); + if (ri == null) { + continue; } + if (ri.isNew) { + ri.isNew = false; + ri.time = currentTime; + } else { + long diff = currentTime - ri.time; + if (diff < duration) { + long nextTime = ri.time + duration; + if (earliestCheckTime == 0 || nextTime < earliestCheckTime) { + earliestCheckTime = nextTime; + } + } else { + allOk = false; + alert(duration, gi, mn); + } + } + } + if (allOk) { + gi.failed = false; } } } + + shortestWait = earliestCheckTime > 0 ? earliestCheckTime - currentTime : 0; } private void alert(final long duration, final GsmItem gi, final String mn) { - String AlertVizMessage = "Watchdog: Radar "; - AlertVizMessage += gi.radarID + " has not produced a '" + mn - + "' product in the last " + duration / 1000 + " seconds."; - RadarServerAvailable.sendNotification(gi.radarID, AlertVizMessage); + if (! gi.failed) { + gi.failed = true; + String AlertVizMessage = "Watchdog: Radar "; + AlertVizMessage += gi.radarID + " has not produced a '" + mn + + "' product in the last " + duration / 1000 + " seconds."; + RadarServerAvailable.sendNotification(gi.radarID, AlertVizMessage); + } } public void notifyWatchdog() { @@ -218,7 +255,8 @@ public class RadarWatchdog extends Thread { private boolean loadConfigFile(final String filename) { try { - InputStream inFile = configuration.getDropInData(filename); + InputStream inFile = radarServer.getConfiguration(). + getDropInData(filename); InputStreamReader reader = new InputStreamReader(inFile); BufferedReader in = new BufferedReader(reader); String line; diff --git a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdogListener.java b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdogListener.java index 74f7a8c215..31d3b130a1 100644 --- a/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdogListener.java +++ b/RadarServer/com.raytheon.rcm.server/src/com/raytheon/rcm/server/RadarWatchdogListener.java @@ -44,6 +44,8 @@ import com.raytheon.rcm.server.RadarWatchdog; * Date Ticket# Engineer Description * ------------- ---------- ----------- -------------------------- * May 12, 2014 DR 16319 dhuffman Initial creation. + * Feb 10, 2015 DR 17112 D. Friedman Only alarm on dedicated radars and + * once per detected failure. * * * @@ -61,8 +63,8 @@ public class RadarWatchdogListener extends RadarEventAdapter { mnemonicMap.put("HSW", "SW"); } - public RadarWatchdogListener(Configuration configuration) { - radarWatchdog = new RadarWatchdog(configuration); + public RadarWatchdogListener(RadarServer radarServer) { + radarWatchdog = new RadarWatchdog(radarServer); radarWatchdog.start(); } @@ -81,25 +83,14 @@ public class RadarWatchdogListener extends RadarEventAdapter { } if (gsm != null) { - RadarWatchdog.GsmItem gi = new RadarWatchdog.GsmItem(); - gi.radarID = event.getRadarID(); - gi.vcp = gsm.vcp; - gi.time = gsm.time.getTimeInMillis(); - - RadarWatchdog.GsmItem oldgi = radarWatchdog - .getGSMItem(gi.radarID); - if (oldgi == null - || (oldgi != null && oldgi.time <= gi.time)) { - radarWatchdog.putGSMItem(gi); - } + radarWatchdog.notifyGsm(event.getRadarID(), gsm.vcp); } } else if (16 <= messageCode) { int mcode = 0; PDB pdb = null; - RadarWatchdog.RadarItem ri = new RadarWatchdog.RadarItem(); mcode = Message.messageCodeOf(msg); - ri.messageTime = (Message.decodeHeader(msg).time) + long messageTime = (Message.decodeHeader(msg).time) .getTimeInMillis(); RadarProduct rp = ProductInfo.getInstance().getPoductForCode( @@ -107,10 +98,9 @@ public class RadarWatchdogListener extends RadarEventAdapter { if (rp == null) return; - ri.mnemonic = rp.mnemonic; - String newMnemonic = mnemonicMap.get(ri.mnemonic); - if (newMnemonic != null) - ri.mnemonic = newMnemonic; + String mnemonic = mnemonicMap.get(rp.mnemonic); + if (mnemonic == null) + mnemonic = rp.mnemonic; try { pdb = GraphicProduct.pdbOfMessage(msg); @@ -120,17 +110,8 @@ public class RadarWatchdogListener extends RadarEventAdapter { } if (pdb != null) { - ri.radarID = event.getRadarID(); - ri.time = System.currentTimeMillis(); - - RadarWatchdog.RadarItem oldri = radarWatchdog.getRadarItem( - ri.mnemonic, ri.radarID); - - if (oldri == null - || (oldri != null && oldri.messageTime <= ri.messageTime)) { - radarWatchdog.putRadarItem(ri); - radarWatchdog.notifyWatchdog(); - } + radarWatchdog.notifyRadarItem(event.getRadarID(), mnemonic, + messageTime, System.currentTimeMillis()); } } } From d9ffd6a7105c0edbeb5f8ccd42a63a7ca8072256 Mon Sep 17 00:00:00 2001 From: Sarah Pontius Date: Thu, 12 Feb 2015 11:38:12 -0700 Subject: [PATCH 18/34] VLab Issue #6386 - HLS/TCV Beta Testing Changes; fixes #6386 Change-Id: Ica6e803b85c34e6d65fd268b844eaac06417909e Former-commit-id: e4a1aa13f57ca775b7b850080f1f6e0168829d92 [formerly 5fbb83dd042795e308ca8b6aa370e4f9b0fb3291] Former-commit-id: 564febcc4aeb2590a8471b4a849fc276c5453af4 --- .../userPython/textProducts/HLSTCV_Common.py | 168 +-- .../gfe/userPython/utilities/TCVDictionary.py | 582 +++++------ .../textproducts/templates/product/HLS.py | 292 ++++-- .../templates/product/Hazard_TCV.py | 976 +++++++++++++----- 4 files changed, 1317 insertions(+), 701 deletions(-) diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py index 8433cc49a9..ec2a4162e8 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textProducts/HLSTCV_Common.py @@ -1,4 +1,4 @@ -# Version 2014.12.17-0 +# Version 2015.2.10-1 import GenericHazards import JsonSupport @@ -19,7 +19,6 @@ class TextProduct(GenericHazards.TextProduct): def __init__(self): GenericHazards.TextProduct.__init__(self) self._pp = pprint.PrettyPrinter() - ############################################################### ### Hazards and Additional Hazards @@ -106,9 +105,9 @@ class TextProduct(GenericHazards.TextProduct): error = self._getVariables(argDict) if error is not None: return error - - self._backupFullStationID = self._fullStationID + self._argDict = argDict + self._productID = self._pil[0:3].upper() argDict["definition"] = self._definition @@ -123,6 +122,7 @@ class TextProduct(GenericHazards.TextProduct): # Set up the areaDictionary for all to use accessor = ModuleAccessor.ModuleAccessor() self._areaDict = accessor.variable(self._areaDictionary, "AreaDictionary") + self._tpc = TextProductCommon() self._tpc.setUp(self._areaDict) @@ -168,7 +168,7 @@ class TextProduct(GenericHazards.TextProduct): def _wmoHeader(self, productDict, productSegmentGroup, arguments=None): headerDict = collections.OrderedDict() headerDict['TTAAii'] = self._wmoID - headerDict['originatingOffice'] = self._backupFullStationID # Will be siteID if not in backup mode + headerDict['originatingOffice'] = self._fullStationID headerDict['productID'] = self._productID headerDict['siteID'] = self._site headerDict['fullStationID'] = self._fullStationID @@ -180,6 +180,7 @@ class TextProduct(GenericHazards.TextProduct): headerDict['disclaimer'] = 'This XML wrapped text product should be considered COMPLETELY EXPERIMENTAL. The National Weather Service currently makes NO GUARANTEE WHATSOEVER that this product will continue to be supplied without interruption. The format of this product MAY CHANGE AT ANY TIME without notice.' headerDict['cityState'] = self._wfoCityState headerDict['stormNumber'] = self._getStormNumberStringFromTCP() + # Modify the product name to indicate test or experimental mode if necessary self._productName = self.checkTestMode( self._argDict, productSegmentGroup.get('productName') + self._areaName) headerDict['productName'] = self._productName @@ -194,13 +195,14 @@ class TextProduct(GenericHazards.TextProduct): ################# Mixed Level def _ugcHeader(self, productDict, productSegmentGroup, productSegment): - productDict['ugcCodes'] = self._formatUGC_entries() - self._ugcHeader_value = self._tpc.formatUGCs(self._ugcs, self._expireTime) - productDict['ugcHeader'] = self._ugcHeader_value + # The UGC header is the formatted list of UGCs along with an expire time + # For example: 'FLZ066>068-071-072-063-069-073>075-168-172>174-070-230515-' + ugcHeader = self._tpc.formatUGCs(self._ugcs, self._expireTime) + productDict['ugcHeader'] = ugcHeader ################# Product Parts Processing - def _processProductParts(self, productGenerator, productDict, productSegmentGroup, productParts): + def _processProductParts(self, productGenerator, productDict, productSegmentGroup, productParts): ''' @param productDict @param productSegmentGroup @@ -249,46 +251,18 @@ class TextProduct(GenericHazards.TextProduct): "Removing product part = %s" % (part), 1) partsList.remove(part) - ################# Product Parts Helper Methods - - def _formatUGC_entries(self): - ugcCodeList = [] - for ugc in self._ugcs: - areaDictEntry = self._areaDict.get(ugc) - if areaDictEntry is None: - # We are not localized correctly for the hazard - # So get the first dictionary entry - self.logger.info('Not Localized for the hazard area -- ugc' + ugc) - keys = self._areaDict.keys() - areaDictEntry = self._areaDict.get(keys[0]) - ugcEntry = collections.OrderedDict() - ugcEntry['state'] = areaDictEntry.get('stateAbbr') - ugcEntry['type'] = self._getUgcInfo(ugc, 'type') - ugcEntry['number'] = self._getUgcInfo(ugc, 'number') - ugcEntry['text'] = ugc - ugcEntry['subArea'] = '' - ugcCodeList.append(ugcEntry) - return ugcCodeList - - def _getUgcInfo(self, ugc, part='type'): - if part == 'type': - if ugc[2] == 'C': - return 'County' - else: - return 'Zone' - if part == 'number': - return ugc[3:] - ############################################################### ### Product Dictionary methods for creating, populating and ### formatting the product dictionary - def _createProductDictionary(self, segmentList): + def _createProductDictionary(self, productPartsGenerator, segments, areProductPartsSegmented): # Create the product dictionary - productSegmentGroup = self._groupSegments(segmentList) + productSegmentGroup = self._groupSegments(productPartsGenerator, + segments, + areProductPartsSegmented) productDict = self._initializeProductDictionary(productSegmentGroup) - productParts = productSegmentGroup.get('productParts') + productParts = productSegmentGroup.get('productParts') productDict['productParts'] = productParts self._processProductParts(self, productDict, productSegmentGroup, productParts) @@ -323,13 +297,9 @@ class TextProduct(GenericHazards.TextProduct): WGUS63 KBOU 080400 FFWBOU - ''' - self._productID = productSegmentGroup.get('productID', 'NNN') + ''' if self._areaName != '': self._areaName = ' FOR ' + self._areaName + '\n' - self._geoType = productSegmentGroup.get('geoType') - self._mapType = productSegmentGroup.get('mapType') - self._productTimeZones = [] # Fill in product dictionary information productDict = collections.OrderedDict() @@ -346,14 +316,26 @@ class TextProduct(GenericHazards.TextProduct): ### Sampling and Statistics related methods def _getStatValue(self, statDict, element, method=None, dataType=None): + + self.debug_print("*"*90, 1) + self.debug_print("In _getStatValue looking for '%s'" % (element), 1) + self.debug_print("statDict =\n%s" % (pprint.pformat(statDict)), 1) + stats = statDict.get(element, None) + self.debug_print("stats =\n%s" % (pprint.pformat(stats)), 1) + if stats is None: return None if type(stats) is types.ListType: stats = stats[0] stats, tr = stats if dataType==self.VECTOR(): stats, dir = stats + return self.getValue(stats, method) + + # Define a class to handle missing statistics + class StatisticsException(Exception): + pass ############################################################### ### Area, Zone and Segment related methods @@ -361,18 +343,47 @@ class TextProduct(GenericHazards.TextProduct): def _allAreas(self): return self._inlandAreas() + self._coastalAreas() + def _groupSegments(self, productPartsGenerator, segments, areProductPartsSegmented): + ''' + Group the segments into the products. The TCV and HLS product generators + only create a single product each so there is only one product segment group. + ''' + + segment_vtecRecords_tuples = self._getSegmentVTECRecordsTuples(segments) + + productSegmentGroup = { + 'productID' : self._productID, + 'productName': self._productName, + 'geoType': 'area', + 'vtecEngine': self._hazardsTable, + 'mapType': 'publicZones', + 'segmented': areProductPartsSegmented, + 'productParts': productPartsGenerator(segment_vtecRecords_tuples), + } + + return productSegmentGroup + + def _getSegmentVTECRecordsTuples(self, segments): + segment_vtecRecords_tuples = [] + for segment in segments: + vtecRecords = self._getVtecRecords(segment) + self.debug_print("vtecRecords for %s =\n\n%s\n" % (segment, self._pp.pformat(vtecRecords))) + segment_vtecRecords_tuples.append((segment, vtecRecords)) + + return segment_vtecRecords_tuples + def _computeIntersectAreas(self, editAreas, argDict): editAreaUtils = EditAreaUtils.EditAreaUtils() editAreaUtils.setUp(None, argDict) surgeEditArea = editAreaUtils.getEditArea("StormSurgeWW_EditArea", argDict) - intersectAreas =[] + intersectAreas = [] for (_, editAreaLabel) in editAreas: editArea = editAreaUtils.getEditArea(editAreaLabel, argDict) intersectAreaLabel = "intersect_"+editAreaLabel intersectArea = editAreaUtils.intersectAreas(intersectAreaLabel, editArea, surgeEditArea) grid = intersectArea.getGrid() - if grid.isAnyBitsSet(): - editAreaUtils.saveEditAreas([intersectArea]) + if grid.isAnyBitsSet(): # Make sure the intersection isn't empty + editAreaUtils.saveEditAreas([intersectArea]) # Register the new edit area with the system intersectAreas.append((intersectAreaLabel, intersectAreaLabel)) return intersectAreas @@ -382,8 +393,7 @@ class TextProduct(GenericHazards.TextProduct): def _initializeHazardsTable(self, argDict): import VTECMessageType - productID = self._pil[0:3] - vtecMode = VTECMessageType.getVTECMessageType(productID.upper()) + vtecMode = VTECMessageType.getVTECMessageType(self._productID) argDict["vtecMode"] = vtecMode self._setVTECActiveTable(argDict) @@ -394,10 +404,8 @@ class TextProduct(GenericHazards.TextProduct): self._hazardsTable = self._getHazardsTable(argDict, self.filterMethod) argDict["hazards"] = self._hazardsTable - def _getHazardsTable(self, argDict, filterMethod, editAreas=None): + def _getHazardsTable(self, argDict, filterMethod): # Set up edit areas as list of lists - # Need to check hazards against all edit areas in the CWA MAOR - argDict["combinations"]= [(self._allAreas(),"Region1")] dfEditAreas = argDict["combinations"] editAreas = [] for area, label in dfEditAreas: @@ -408,21 +416,19 @@ class TextProduct(GenericHazards.TextProduct): else: editAreas.append([area]) # Get Product ID and other info for HazardsTable - pil = self._pil.upper() # Ensure PIL is in UPPERCASE stationID4 = self._fullStationID - productCategory = pil[0:3] #part of the pil + productCategory = self._productID definition = argDict['definition'] sampleThreshold = definition.get("hazardSamplingThreshold", (10, None)) # Process the hazards accurateCities = definition.get('accurateCities', 0) - cityRefData = [] import HazardsTable hazards = HazardsTable.HazardsTable( argDict["ifpClient"], editAreas, productCategory, filterMethod, argDict["databaseID"], stationID4, argDict["vtecActiveTable"], argDict["vtecMode"], sampleThreshold, creationTime=argDict["creationTime"], accurateCities=accurateCities, - cityEditAreas=cityRefData, dataMgr=argDict['dataMgr']) + cityEditAreas=[], dataMgr=argDict['dataMgr']) return hazards def _ignoreActions(self): @@ -443,11 +449,20 @@ class TextProduct(GenericHazards.TextProduct): else: argDict["vtecActiveTable"] = "active" - def _getVtecRecords(self, segment, vtecEngine=None): + def _getVtecRecords(self, segment): vtecRecords = self._hazardsTable.getHazardList(segment) + # Tropical hazards shouldn't ever have EXT and EXB actions since + # they are "until further notice" + for record in vtecRecords: + if record['act'] == "EXT": + record['act'] = "CON" + elif record['act'] == "EXB": + record['act'] = "EXA" + return vtecRecords def _getAllowedHazardList(self, allowedHazardList=None): + # Get the list of allowed phenSigs (ie. "HU.W") if allowedHazardList is None: allowedHazardList = self.allowedHazards() hazardList = [] @@ -588,8 +603,8 @@ class TextProduct(GenericHazards.TextProduct): self._ddhhmmTime = self.getCurrentTime( argDict, "%d%H%M", shiftToLocal=0, stripLeading=0) - self._currentTime = self._issueTime_secs - self._expireTime = self._issueTime_secs + self._purgeTime*3600 + self._purgeHours = self._purgeTime + self._expireTime = self._issueTime_secs + self._purgeHours*3600 self._timeLabel = self.getCurrentTime( argDict, "%l%M %p %Z %a %b %e %Y", stripLeading=1) @@ -600,14 +615,13 @@ class TextProduct(GenericHazards.TextProduct): startTime = self._calculateStartTime(time.gmtime(self._issueTime_secs)) self._timeRange = self.makeTimeRange(startTime, startTime+120*3600) - # Create a time range to look from the current time back 12 hours - # We will use this are to determine if we need to use "additional" - # wording with rainfall + # Create a time range to look from the current time back 12 hours. + # We will use this to determine if we need to use "additional" + # wording with rainfall for the TCV self._extraSampleTimeRange = self.makeTimeRange(startTime-12*3600, startTime) - # Determine the time range list, making sure they are on hour boundaries - # w.r.t. midnight today according to the resolution + # Determine the time range list according to the resolution subRanges = self.divideRange(self._timeRange, self._resolution()) trList = [] self._periodList = [] @@ -617,6 +631,7 @@ class TextProduct(GenericHazards.TextProduct): trList.append((tr, "Label")) if index == 0: + # Create the 10 periods startTime = tr.startTime() localtime = time.localtime(startTime.unixTime()) @@ -1027,8 +1042,9 @@ FORECASTER STEWART""" self._loadLastTwoAdvisories() def _synchronizeAdvisories(self): - - # Retrieving a directory causes synching to occur + # Retrieving a directory causes synching to occur. + # This code can throw an exception but don't catch it + # so that forecasters can be made aware of the issue. file = LocalizationSupport.getLocalizationFile(LocalizationSupport.CAVE_STATIC, LocalizationSupport.SITE, self._site, self._getAdvisoryPath()).getFile() @@ -1091,7 +1107,7 @@ FORECASTER STEWART""" self._previousPreviousAdvisory = None if len(lastTwoAdvisories) >= 2: self._previousPreviousAdvisory = self._loadAdvisory(lastTwoAdvisories[1]) - + def _loadAdvisory(self, advisoryName): self._synchronizeAdvisories() fileName = self._getAdvisoryFilename(advisoryName) @@ -1370,10 +1386,10 @@ class TextProductCommon(DiscretePhrases.DiscretePhrases): if laterActive is not None: expireTime = min(expireTime, laterActive) elif canExpFound and not activeFound: - expireTime = min(expireTime, issueTime+3600) #1hr from now + expireTime = min(expireTime, issueTime+3600*1000) #1hr from now #ensure expireTime is not before issueTime, and is at least 1 hour - if expireTime - issueTime < 3600: + if expireTime - issueTime < 3600*1000: expireTime = issueTime + 3600*1000 #round to next 'roundMinutes' @@ -1449,7 +1465,9 @@ class TextProductCommon(DiscretePhrases.DiscretePhrases): def formatUGCs(self, ugcs, expireTime): ''' Create ugc header with expire time - 'COC123-112330-' + Examples: + 'COC123-112330-' + 'FLZ066>068-071-072-063-069-073>075-168-172>174-070-230515-' ''' ugcStr = self.makeUGCString(ugcs) ddhhmmTime = self.getFormattedTime( @@ -1546,6 +1564,10 @@ class TextProductCommon(DiscretePhrases.DiscretePhrases): def makeUGCString(self, ugcs): ''' Create the UGC string for product / segment headers. + + Examples: + FLZ173- + FLZ066>068-071-072-063-069-073>075-168-172>174-070- ''' # if nothing in the list, return empty string if len(ugcs) == 0: diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/TCVDictionary.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/TCVDictionary.py index 4af55bc76c..f2ee8a646d 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/TCVDictionary.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/TCVDictionary.py @@ -5,6 +5,7 @@ # TCV_Dictionary # TCV_Dictionary file # Author: GFE Installation Script +# Last Modified: Feb 13, 2015 # ---------------------------------------------------------------------------- # Needed to prevent an error from the SmartTool module WeatherElementEdited = None @@ -13,83 +14,83 @@ ThreatStatements = { "Wind": { "Extreme": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity.", + "planning": "Emergency planning should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3 intensity or higher.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic wind impacts. Efforts should now be underway to secure all properties.", - "action": "Life threatening wind is possible. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering.", + "action": "Extremely Dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity.", + "planning": "Emergency plans should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3 intensity or higher.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic wind impacts. Remaining efforts to secure properties should now be brought to completion.", - "action": "Life threatening wind is possible. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering. Move to safe shelter before the wind becomes hazardous.", + "action": "Extremely dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering. Move to safe shelter before the wind becomes hazardous.", }, "hunker down": { - "planning": "Brace against the reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3, 4, or 5 intensity. Maintain readiness for emergency response.", - "preparation": "To be safe, last minute efforts should fully focus on protecting life. Efforts to secure properties against devastating to catastrophic wind impacts should now be complete.", - "action": "Life threatening wind is imminent or ongoing. Now is the time to urgently hide from the wind. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering. Remain sheltered until the hazardous wind subsides. Be ready to quickly move to the safest place within your shelter if extreme wind warnings are issued.", + "planning": "Remain braced against the reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3 intensity or higher.", + "preparation": "To be safe, efforts should fully focus on protecting life. Properties remain subject to devastating to catastrophic wind impacts.", + "action": "Now is the time to urgently hide from the wind. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering. Remain sheltered until the hazardous wind subsides. Be ready to quickly move to the safest place within your shelter if extreme wind warnings are issued.", }, "recovery": { - "planning": "The threat of hazardous wind has subsided.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", - "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + "planning": "The threat for hazardous wind has subsided.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injury or loss of life. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for major hurricane force wind greater than 110 MPH of equivalent Category 3 or higher.", + "preparation": "Be safe and aggressively protect against the potential of devastating to catastrophic wind impacts.", + "action": "Extremely dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury, loss of life, or immense human suffering.", }, }, "High": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", + "planning": "Emergency planning should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 to 2 intensity.", "preparation": "To be safe, aggressively prepare for the potential of extensive wind impacts. Efforts should now be underway to secure all properties.", - "action": "Life threatening wind is possible. Failure to adequately shelter may result in serious injury or loss of life.", + "action": "Dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury or loss of life.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", + "planning": "Emergency plans should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 to 2 intensity.", "preparation": "To be safe, aggressively prepare for the potential of extensive wind impacts. Remaining efforts to secure properties should now be brought to completion.", - "action": "Life threatening wind is possible. Failure to adequately shelter may result in serious injury or loss of life. Move to safe shelter before the wind becomes hazardous.", + "action": "Dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury or loss of life. Move to safe shelter before the wind becomes hazardous.", }, "hunker down": { - "planning": "Brace against the reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 or 2 intensity.", - "preparation": "To be safe, last minute efforts should fully focus on protecting life. Efforts to secure properties against extensive wind impacts should now be complete.", - "action": "Life threatening wind is imminent or ongoing. Now is the time to urgently hide from the wind. Failure to adequately shelter may result in serious injury or loss of life. Remain sheltered until the hazardous wind subsides.", + "planning": "Remain braced against the reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 to 2 intensity.", + "preparation": "To be safe, efforts should fully focus on protecting life. Properties remain subject to extensive wind impacts.", + "action": "Now is the time to urgently hide from the wind. Failure to adequately shelter may result in serious injury or loss of life. Remain sheltered until the hazardous wind subsides.", }, "recovery": { - "planning": "The threat for hazardous wind has subsided.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", - "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + "planning": "The threat for hazardous wind has subsided.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injury or loss of life. If you have a life-threatening emergency dial 9 1 1." , }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for hurricane force wind of 74 to 110 MPH of equivalent Category 1 to 2 intensity.", + "preparation": "Be safe and aggressively protect against for the potential of extensive wind impacts.", + "action": "Dangerous and life threatening wind is possible. Failure to adequately shelter may result in serious injury or loss of life.", }, }, "Mod": { "check plans": { "planning": "Emergency planning should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", "preparation": "To be safe, earnestly prepare for the potential of significant wind impacts. Efforts should now be underway to secure all properties.", - "action": "Failure to adequately shelter may result in serious injury, or in some cases loss of life.", + "action": "Dangerous wind is possible. Failure to adequately shelter may result in serious injury, or in some cases loss of life.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "planning": "Emergency plans should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", "preparation": "To be safe, earnestly prepare for the potential of significant wind impacts. Remaining efforts to secure properties should now be brought to completion.", "action": "Dangerous wind is possible. Failure to adequately shelter may result in serious injury, or in some cases loss of life. Move to safe shelter before the wind becomes hazardous.", }, "hunker down": { - "planning": "Brace against the reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", - "preparation": "To be safe, last minute efforts should fully focus on protecting life. Efforts to secure properties against significant wind impacts should now be complete.", - "action": "Dangerous wind is imminent or ongoing. Now is the time to hide from the wind. Failure to adequately shelter may result in serious injury, or in some cases loss of life. Remain sheltered until the hazardous wind subsides.", + "planning": "Remain braced against the reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "preparation": "To be safe, efforts should fully focus on protecting life. Properties remain subject to significant wind impacts.", + "action": "Now is the time to hide from the wind. Failure to adequately shelter may result in serious injury, or in some cases loss of life. Remain sheltered until the hazardous wind subsides.", }, "recovery": { - "planning": "The threat for hazardous wind has subsided.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", - "action": "Failure to exercise due safety may result in additional injuries, or in some cases loss of life.", + "planning": "The threat for hazardous wind has subsided.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injury, or in some cases loss of life. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for strong tropical storm force wind of 58 to 73 MPH.", + "preparation": "Be safe and earnestly protect against the potential of significant wind impacts.", + "action": "Dangerous wind is possible. Failure to adequately shelter may result in serious injury, or in some cases loss of life.", }, }, "Elevated": { @@ -99,49 +100,49 @@ ThreatStatements = { "action": "Hazardous wind is possible. Failure to adequately shelter may result in serious injury.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "planning": "Emergency plans should include a reasonable threat for tropical storm force wind of 39 to 57 MPH.", "preparation": "To be safe, prepare for the potential of limited wind impacts. Remaining efforts to secure properties should now be brought to completion.", "action": "Hazardous wind is possible. Failure to adequately shelter may result in serious injury. Move to safe shelter before the wind becomes hazardous.", }, "hunker down": { - "planning": "Brace against the reasonable threat for tropical storm force wind of 39 to 57 MPH. Maintain a readiness for emergency response.", - "preparation": "To be safe, last minute efforts should fully focus on avoiding injury. Efforts to secure properties against limited wind impacts should now be complete.", - "action": "Hazardous wind is imminent or ongoing. Now is the time to hide from the wind. Failure to adequately shelter may result in serious injury. Remain sheltered until the hazardous wind subsides.", + "planning": "Remain braced against the reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "preparation": "To be safe, efforts should fully focus on avoiding injury. Properties remain subject to limited wind impacts.", + "action": "Now is the time to hide from the wind. Failure to adequately shelter may result in serious injury. Remain sheltered until the hazardous wind subsides.", }, "recovery": { - "planning": "The threat for hazardous wind has subsided.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Stay out of restricted areas.", - "action": "Failure to exercise due safety may result in additional injuries.", + "planning": "The threat for hazardous wind has subsided.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Stay out of restricted areas.", + "action": "Failure to exercise due safety may result in additional injury. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for tropical storm force wind of 39 to 57 MPH.", + "preparation": "Be safe and protect against the potential of limited wind impacts.", + "action": "Hazardous wind is possible. Failure to adequately shelter may result in serious injury.", }, }, "None": { "check plans": { "planning": "Emergency planning for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", - "preparation": "Little to no preparations needed to guard against tropical wind.", - "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + "preparation": "Little to no preparations needed to guard against tropical winds at this time.", + "action": "Ensure readiness for the next tropical wind event.", }, "complete preparations": { - "planning": "Emergency planning for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", - "preparation": "Little to no preparations needed to guard against tropical wind.", - "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + "planning": "Emergency plans for this event need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", + "preparation": "Little to no preparations needed to guard against tropical winds at this time.", + "action": "Ensure readiness for the next tropical wind event.", }, "hunker down": { - "planning": "The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", - "preparation": "Little to no preparations needed to guard against tropical wind.", - "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", + "planning": "Emergency considerations need not include a threat for tropical storm force wind. The wind will remain less than 39 MPH, but conditions may still be breezy to windy.", + "preparation": "Little to no preparations needed to guard against tropical winds at this time.", + "action": "Ensure readiness for the next tropical wind event.", }, "recovery": { - "planning": "Conditions may be still breezy to windy.", + "planning": "Conditions may still be breezy to windy.", "preparation": "Exercise due safety when moving about.", "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", }, - "nothing to see here": { - "planning": "Conditions may be breezy to windy.", + "default": { + "planning": "Conditions may still be breezy to windy.", "preparation": "Exercise due safety when moving about.", "action": "Review your seasonal plan and ensure readiness for the next tropical wind event.", }, @@ -152,134 +153,134 @@ ThreatStatements = { "check plans": { "planning": "Emergency planning should include a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic storm surge flooding impacts. Evacuation efforts should now be underway.", - "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave immediately if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", + "planning": "Emergency plans should include a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic storm surge flooding impacts. Evacuation efforts should now be brought to completion. Evacuations must be complete before driving conditions become unsafe.", - "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave immediately if evacuation orders have been given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or immense human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", }, "hunker down": { - "planning": "Emergency response should posture for a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", - "preparation": "To be safe, evacuees should now be located within prescribed shelters and well away from deadly storm surge flooding capable of devastating to catastrophic impacts.", - "action": "Life threatening inundation is imminent or ongoing. Failure to have heeded evacuation orders may result in serious injury, significant loss of life, or immense human suffering.", + "planning": "Emergency considerations should posture for a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", + "preparation": "To be safe, evacuees should be located within prescribed shelters and well away from deadly storm surge flooding capable of devastating to catastrophic impacts.", + "action": "Life threatening inundation is possible. Those who failed to heed evacuation orders risk serious injury, significant loss of life, or immense human suffering.", }, "recovery": { "planning": "The threat of deadly storm surge is abating as flood waters recede.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", - "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injury or loss of life. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for extreme storm surge flooding greater than 9 feet above ground.", + "preparation": "Be safe and aggressively guard against the potential of devastating to catastrophic storm surge flooding impacts.", + "action": "Life threatening inundation is possible. Failure to heed official instructions may result in serious injury, significant loss of life, or immense human suffering. Poor decisions may result in being cut off or needlessly risk lives.", }, }, "High": { "check plans": { "planning": "Emergency planning should include a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", - "preparation": "To be safe, aggressively prepare for the potential of extensive storm surge flooding impacts. Evacuate efforts should now be underway.", - "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", + "preparation": "To be safe, aggressively prepare for the potential of extensive storm surge flooding impacts. Evacuation efforts should now be underway.", + "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", + "planning": "Emergency plans should include a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", "preparation": "To be safe, aggressively prepare for the potential of extensive storm surge flooding impacts. Evacuation efforts should now be brought to completion. Evacuations must be complete before driving conditions become unsafe.", "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury, significant loss of life, or human suffering. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may result in being cut off or needlessly risk lives.", }, "hunker down": { - "planning": "Emergency response should posture for a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", - "preparation": "To be safe, evacuees should now be located within prescribed shelters and well away from deadly storm surge flooding capable of extensive impacts.", - "action": "Life threatening inundation is imminent or ongoing. Failure to have heeded evacuation orders may result in serious injury, significant loss of life, or human suffering.", + "planning": "Emergency considerations should posture for a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", + "preparation": "To be safe, evacuees should be located within prescribed shelters and well away from deadly storm surge flooding capable of extensive impacts.", + "action": "Life threatening inundation is possible. Those who failed to heed evacuation orders risk serious injury, significant loss of life, or human suffering.", }, "recovery": { - "planning": "The threat of deadly storm surge is abating as flood waters recede.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", - "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + "planning": "The threat of deadly storm surge is abating as flood waters recede.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injury or loss of life. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for major storm surge flooding of 6 to 9 feet above ground.", + "preparation": "Be safe and aggressively guard against the potential of extensive storm surge flooding impacts.", + "action": "Life threatening inundation is possible. Failure to heed official instructions may result in serious injury, significant loss of life, or human suffering. Poor decisions may result in being cut off or needlessly risk lives.", }, }, "Mod": { "check plans": { "planning": "Emergency planning should include a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", "preparation": "To be safe, earnestly prepare for the potential of significant storm surge flooding impacts. Evacuation efforts should now be underway.", - "action": "Life threatening inundation is possible. Failure to heed evacuation orders or instructions from local officials may result in serious injury or loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", + "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury or loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", + "planning": "Emergency plans should include a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", "preparation": "To be safe, earnestly prepare for the potential of significant storm surge flooding impacts. Evacuation efforts should now be brought to completion. Evacuations must be complete before driving conditions become unsafe.", - "action": "Life threatening inundation is possible. Failure to heed evacuation orders or instructions from local officials may result in serious injury or loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", + "action": "Life threatening inundation is possible. Failure to heed evacuation orders may result in serious injury or loss of life. Leave if evacuation orders are given for your area. Consider voluntary evacuation if recommended. Poor decisions may needlessly risk lives.", }, "hunker down": { - "planning": "Emergency response should posture for a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", - "preparation": "To be safe, evacuees should now be located within prescribed shelters and well away from storm surge flooding capable of significant impacts.", - "action": "Life threatening inundation is imminent or ongoing. Failure to have heeded evacuation orders or instructions from local officials may result in serious injury or loss of life.", + "planning": "Emergency considerations should posture for a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", + "preparation": "To be safe, evacuees should be located within prescribed shelters and well away from storm surge flooding capable of significant impacts.", + "action": "Life threatening inundation is possible. Those who failed to heed evacuation orders risk serious injury or loss of life.", }, "recovery": { - "planning": "The threat of dangerous storm surge is abating as flood waters recede.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", - "action": "Failure to exercise due safety may result in additional injuries or loss of life.", + "planning": "The threat of dangerous storm surge is abating as flood waters recede.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Do not return to evacuated areas until flood waters completely recede and the all-clear is officially given.", + "action": "Failure to exercise due safety may result in additional injury or loss of life. If you have a life-threatening emergency dial 9 1 1.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for dangerous storm surge flooding of 3 to 6 feet above ground.", + "preparation": "Be safe and earnestly guard against the potential of significant storm surge flooding impacts.", + "action": "Life threatening inundation is possible. Failure to heed official instructions may result in serious injury or loss of life. Poor decisions may needlessly risk lives.", }, }, "Elevated": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", - "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Preparedness efforts should be underway.", - "action": "Localized inundation is possible. Follow the instructions of local officials. Consider voluntary evacuation if recommended. Leave if evacuation orders are issued.", + "planning": "Emergency planning should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Efforts should now be underway.", + "action": "Localized inundation is possible. Follow the instructions of local officials. Consider voluntary evacuation if recommended. Leave if evacuation orders are issued.", }, "complete preparations": { - "planning": "Adjustments to emergency plans should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", - "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Preparedness efforts should now be brought to completion before conditions deteriorate.", + "planning": "Emergency plans should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "To be safe, prepare for the potential of limited storm surge flooding impacts. Efforts should now be brought to completion before conditions deteriorate.", "action": "Localized inundation is possible. Follow the instructions of local officials. Consider voluntary evacuation if recommended. Leave immediately if evacuation orders are issued.", }, "hunker down": { - "planning": "Emergency response should posture for a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "planning": "Emergency considerations should posture for a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", "preparation": "To be safe, stay away from storm surge flooding capable of limited impacts.", - "action": "Localized inundation is imminent or ongoing. Continue to follow the instructions of local officials.", + "action": "Localized inundation is possible. Continue to follow the instructions of local officials.", }, "recovery": { - "planning": "The threat of hazardous storm surge is abating as flood waters recede.", - "preparation": "To be safe, heed the instructions of local officials when moving about. Do not return to flooded areas until the all-clear is officially given.", - "action": "Exercise due safety.", + "planning": "The threat of hazardous storm surge is abating as flood waters recede.", + "preparation": "Be safe and heed the instructions of local officials when moving about. Do not enter flooded areas.", + "action": "Exercise due safety.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for peak storm surge flooding of 1 to 3 feet above ground.", + "preparation": "Be safe and guard against the potential of limited storm surge flooding impacts.", + "action": "Localized inundation is possible. Follow the instructions of local officials.", }, }, "None": { "check plans": { - "planning": "Emergency planning for this event need not include a threat for storm surge flooding. The ground will remain largely unflooded from surge water or only have spots minimally affected by surge encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", - "preparation": "Little to no preparations needed to guard against storm surge flooding.", - "action": "Review your seasonal plan and ensure readiness for the next storm surge event.", + "planning": "Emergency planning for this event need not include a threat for storm surge flooding. The ground will remain largely unflooded from surge water or only have spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Little to no preparations needed to guard against storm surge flooding at this time.", + "action": "Ensure readiness for the next storm surge event.", }, "complete preparations": { - "planning": "Emergency planning for this event need not include a threat for storm surge flooding. The ground will remain largely unflooded from surge water or only have spots minimally affected by surge encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", - "preparation": "Little to no preparations needed to guard against storm surge flooding.", - "action": "Review your seasonal plan and ensure readiness for the next storm surge event.", + "planning": "Emergency plans for this event need not include a threat for storm surge flooding. The ground will remain largely unflooded from surge water or only have spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Little to no preparations needed to guard against storm surge flooding at this time.", + "action": "Ensure readiness for the next storm surge event.", }, "hunker down": { - "planning": "The ground will remain largely unflooded from surge water or only have spots minimally affected by surge encroachment. Surg conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", - "preparation": "Little to no preparations needed to guard against storm surge flooding.", - "action": "Review your seasonal plan and ensure readiness for the next storm surge event.", + "planning": "Emergency considerations for this event need not include a threat for storm surge flooding. The ground will remain largely unflooded from surge water or only have spots minimally affected by surge water encroachment. Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Little to no preparations needed to guard against storm surge flooding at this time.", + "action": "Ensure readiness for the next storm surge event.", }, "recovery": { - "planning": "Surf conditions may be rough with some beach erosion. Stronger than normal rip currents may also be present.", - "preparation": "Exercise due safety.", + "planning": "Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Exercise due safety.", "action": "Review your seasonal plan and ensure readiness for the next storm surge event.", }, - "nothing to see here": { - "planning": "Surf conditions may be rough with some beach erosion. Stronger than normal rip currents may also be present. ", - "preparation": "Exercise due safety.", + "default": { + "planning": "Surf conditions may still be rough with some beach erosion. Stronger than normal rip currents may also be present.", + "preparation": "Exercise due safety.", "action": "Review your seasonal plan and ensure readiness for the next storm surge event.", }, }, @@ -287,273 +288,270 @@ ThreatStatements = { "Flooding Rain": { "Extreme": { "check plans": { - "planning": "Emergency planning should include a reasonable threat of extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely. ", + "planning": "Emergency planning should include a reasonable threat of extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, or human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, or human suffering. If flood related watches and warnings are issued, heed recommended actions. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground before flood waters arrive.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely.", + "planning": "Emergency plans should include a reasonable threat of extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely.", "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, or human suffering. If flood related watches and warnings are issued, heed recommended actions. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground before flood waters arrive.", }, "hunker down": { - "planning": "Emergency plans should include a reasonable threat for extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely.", - "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. ", + "action": "If flood related watches and warnings are in effect, heed recommended actions.", }, "recovery": { - "planning": "Emergency plans should include a reasonable threat for extreme flooding where peak rainfall totals vastly exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are very likely.", - "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury, significant loss of life, and human suffering. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", - }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed and do not let down your guard.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Do not drive through existing flood waters that cover the road.", }, + "default": { + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", }, }, "High": { "check plans": { "planning": "Emergency planning should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are likely.", "preparation": "To be safe, aggressively prepare for the potential of extensive flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flood related watches and warnings are issued, heed recommended actions. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground before flood waters arrive.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are likely.", - "preparation": "To be safe, aggressively prepare for the potential of extensive flooding rain impacts. Life threatening flooding possible from excessive tropical rain.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground.", + "planning": "Emergency plans should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are likely.", + "preparation": "To be safe, aggressively prepare for the potential of extensive flooding rain impacts.", + "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flood related watches and warnings are issued, heed recommended actions. Poor decisions may result in being cut off or needlessly risk lives. If vulnerable, relocate to safe shelter on higher ground before flood waters arrive.", }, "hunker down": { - "planning": "Emergency plans should include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation.", - "preparation": "To be safe, remain prepared for the potential of extensive flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts.", + "action": "If flood related watches and warnings are in effect, heed recommended actions.", }, "recovery": { - "planning": "Emergency plans should continue to include a reasonable threat for major flooding where peak rainfall totals well exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are likely.", - "preparation": "To be safe, remain prepared for the potential of extensive flooding rain impacts.", - "action": "Life threatening flooding is possible. Failure to take action may result in serious injury or significant loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed and do not let down your guard.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Do not drive through existing flood waters that cover the road.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", }, }, "Mod": { "check plans": { "planning": "Emergency planning should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are possible.", "preparation": "To be safe, earnestly prepare for the potential of significant flooding rain impacts.", - "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flood related watches and warnings are issued, heed recommended actions.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are possible.", - "preparation": "To be safe, earnestly prepare for the potential of significant flooding rain impacts.", - "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", - }, - "hunker down": { "planning": "Emergency plans should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are possible.", - "preparation": "To be safe, remain prepared for the potential of significant flooding rain impacts.", - "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "preparation": "To be safe, earnestly prepare for the potential of significant flooding rain impacts.", + "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flood related watches and warnings are issued, heed recommended actions.", }, + "hunker down": { + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts.", + "action": "If flood related watches and warnings are in effect, heed recommended actions.", }, "recovery": { - "planning": "Emergency plans should include a reasonable threat for moderate flooding where peak rainfall totals notably exceed amounts conducive for flash flooding and rapid inundation. Rescues and emergency evacuations are possible.", - "preparation": "To be safe, remain prepared for the potential of significant flooding rain impacts.", - "action": "Dangerous flooding is possible. Failure to take action may result in serious injury or loss of life. If flash flood watches and warnings are issued, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed and do not let down your guard.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Do not drive through existing flood waters that cover the road.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", }, }, "Elevated": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "planning": "Emergency planning should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for localized flash flooding and rapid inundation.", "preparation": "To be safe, prepare for the potential of limited flooding rain impacts.", - "action": "Localized flooding is possible. If flash flood watches and warnings are issued, heed recommended actions.", + "action": "Localized flooding is possible. If flood related watches and warnings are issued, heed recommended actions.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", + "planning": "Emergency plans should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for localized flash flooding and rapid inundation.", "preparation": "To be safe, prepare for the potential of limited flooding rain impacts.", - "action": "Localized flooding is possible. If flash flood watches and warnings are issued, heed recommended actions.", + "action": "Localized flooding is possible. If flood related watches and warnings are issued, heed recommended actions.", }, "hunker down": { - "planning": "Emergency plans should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", - "preparation": "To be safe, remain prepared for the potential of limited flooding rain impacts.", - "action": "Localized flooding is possible. If flash flood watches and warnings are issued, heed recommended actions.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts.", + "action": "If flood related watches and warnings are in effect, heed recommended actions.", }, "recovery": { - "planning": "Emergency plans should include a reasonable threat for minor flooding where peak rainfall totals are near amounts conducive for flash flooding and rapid inundation.", - "preparation": "To be safe, remain prepared for the potential of limited flooding rain impacts.", - "action": "Localized flooding is possible. If flash flood watches and warnings are issued, heed recommended actions.", + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed and do not let down your guard.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers. Do not drive through existing flood waters that cover the road.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a threat of flooding.", + "preparation": "Be safe and remain ready to protect against flooding rain impacts. Stay informed.", + "action": "If flood related watches and warnings are in effect, heed recommended actions. Also listen for possible river flood warnings for longer-term impacts along rivers.", }, }, "None": { "check plans": { - "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "planning": "Emergency planning need not include a threat for rainfall flooding. Locally heavy rain and nuisance flooding may still occur.", "preparation": "Little to no preparations needed to guard against excessive tropical rainfall.", - "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + "action": "Ensure readiness for the next tropical rainfall event.", }, "complete preparations": { - "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "planning": "Emergency plans need not include a threat for rainfall flooding. Locally heavy rain and nuisance flooding may still occur.", "preparation": "Little to no preparations needed to guard against excessive tropical rainfall.", - "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + "action": "Ensure readiness for the next tropical rainfall event.", }, "hunker down": { - "planning": "Emergency planning for this event need not include a threat for rainfall flooding. Heavy rain and nuisance flooding may still occur.", + "planning": "Emergency considerations need not include a threat for rainfall flooding. Locally heavy rain and nuisance flooding may still occur.", "preparation": "Little to no preparations needed to guard against excessive tropical rainfall.", - "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + "action": "Ensure readiness for the next tropical rainfall event.", }, "recovery": { - "planning": "Heavy rain and nuisance flooding may still occur. ", + "planning": "Locally heavy rain and nuisance flooding may still occur.", "preparation": "Exercise due safety.", - "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall event.", }, - "nothing to see here": { - "planning": "Heavy rain and nuisance flooding may still occur.", + "default": { + "planning": "Locally Heavy rain and nuisance flooding may still occur.", "preparation": "Exercise due safety.", - "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall flooding event.", + "action": "Review your seasonal plan and ensure readiness for the next tropical rainfall event.", }, }, }, "Tornado": { "Extreme": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", - "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts. Those living in mobile homes should relocate to more substantial shelter. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "planning": "Emergency planning should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts. Those living in mobile homes should relocate to more substantial shelter before severe weather arrives.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", - "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts. Those living in mobile homes should relocate to more substantial shelter. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "planning": "When implementing emergency plans, include a reasonable threat for an outbreak of tornadoes." , + "preparation": "To be safe, aggressively prepare for the potential of devastating to catastrophic tornado impacts. Those living in mobile homes should relocate to more substantial shelter before severe weather arrives.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "hunker down": { - "planning": "Emergency plans should include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", - "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic tornado impacts. Stay informed and listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado warnings are issued for your area, quickly move to the safest place within your shelter. Seconds can save lives.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. If a tornado approaches, quickly move to the safest place within your shelter.", }, "recovery": { - "planning": "Emergency plans should continue to include a reasonable threat for an outbreak of tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths. Numerous tornadoes may occur within short periods of time and in close proximity of one another.", - "preparation": "To be safe, remain prepared for the potential of devastating to catastrophic tornado impacts. Stay informed and do not let down your guard/", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed and do not let down your guard.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for an outbreak of tornadoes.", + "preparation": "Be safe and remain ready to protect against the potential of devastating to catastrophic tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, }, "High": { "check plans": { "planning": "Emergency planning should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", - "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts. Those living in mobile homes should relocate to more substantial shelter. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts. Those living in mobile homes should relocate to more substantial shelter before severe weather arrives.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", - "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts. Those living in mobile homes should relocate to more substantial shelter. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "planning": "When implementing emergency plans, include a reasonable threat for numerous tornadoes.", + "preparation": "To be safe, aggressively prepare for the potential of extensive tornado impacts. Those living in mobile homes should relocate to more substantial shelter before severe weather arrives.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "hunker down": { - "planning": "Emergency plans should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", - "preparation": "To be safe, remain prepared for the potential of extensive tornado impacts. Stay informed and listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado warnings are issued for your area, quickly move to the safest place within your shelter. Seconds can save lives.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. If a tornado approaches, quickly move to the safest place within your shelter.", }, "recovery": { - "planning": "Emergency plans should include a reasonable threat for numerous tornadoes, with several possibly strong or violent in intensity and with longer and wider damage paths.", - "preparation": "To be safe, remain prepared for the potential of extensive tornado impacts. Stay informed and do not let down your guard.", - "action": "Failure to adequately shelter may result in serious injury or significant loss of life. If tornado watches and warnings are issued, heed recommended actions.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed and do not let down your guard.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for numerous tornadoes.", + "preparation": "Be safe and remain ready to protect against the potential of extensive tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, }, "Mod": { "check plans": { "planning": "Emergency planning should include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", - "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", - "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", - }, + "planning": "When implementing emergency plans, include should include a reasonable threat for scattered tornadoes.", + "preparation": "To be safe, earnestly prepare for the potential of significant tornado impacts.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "hunker down": { - "planning": "Emergency planning should continue to include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", - "preparation": "To be safe, remain prepared for the potential of significant tornado impacts. Stay informed and listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado warnings are issued for your area, quickly move to the safest place within your shelter. Seconds can save lives.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. If a tornado approaches, quickly move to the safest place within your shelter.", }, "recovery": { - "planning": "Emergency planning should include a reasonable threat for scattered tornadoes, with a few possibly strong in intensity.", - "preparation": "To be safe, remain prepared prepare for the potential of significant tornado impacts. Stay informed and do not let down your guard.", - "action": "Failure to adequately shelter may result in serious injury or loss of life. If tornado watches and warnings are issued for your area, heed recommended actions.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed and do not let down your guard.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for scattered tornadoes.", + "preparation": "Be safe and remain ready to protect against the potential of significant tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, }, "Elevated": { "check plans": { - "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", - "preparation": "To be safe, prepare for the potential of limited tornado impacts. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", + "preparation": "To be safe, prepare for the potential of limited tornado impacts.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "complete preparations": { - "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", - "preparation": "To be safe, prepare for the potential of limited tornado impacts. Listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. Keep a watchful eye to the sky and a listening ear for warning alerts. Be ready to find shelter quickly.", + "planning": "When implementing emergency plans, include a reasonable threat for isolated tornadoes.", + "preparation": "To be safe, prepare for the potential of limited tornado impacts.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, "hunker down": { - "planning": "Emergency planning should include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", - "preparation": "To be safe, remain prepared for the potential of limited tornado impacts. Stay informed and listen for tornado watches and warnings.", - "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado warnings are issued for your area, quickly move to the safest place within your shelter. Seconds can save lives.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. If a tornado approaches, quickly move to the safest place within your shelter.", }, "recovery": { - "planning": "Emergency planning should continue to include a reasonable threat for isolated tornadoes, mostly with shorter and narrower damage paths.", - "preparation": "To be safe, remain prepared for the potential of limited tornado impacts. Stay informed and do not let down your guard.", - "action": "Failure to adequately shelter may result in serious injury, and in some cases loss of life. If tornado watches and warnings are issued for your area, heed recommended actions.", + "planning": "Emergency considerations should include a reasonable threat for tornadoes.", + "preparation": "Be safe and remain ready to protect against tornado impacts. Stay informed and do not let down your guard.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, - "nothing to see here": { - "planning": "", - "preparation": "", - "action": "", + "default": { + "planning": "Emergency considerations should include a reasonable threat for isolated tornadoes.", + "preparation": "Be safe and remain ready to protect against the potential of limited tornado impacts. Stay informed.", + "action": "Listen for tornado watches and warnings. Be ready to shelter quickly if a tornado approaches.", }, }, "None": { "check plans": { - "planning": "Emergency planning for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "planning": "Emergency planning need not include a threat for tornadoes. Showers and thunderstorms with strong gusty winds may still occur.", "preparation": "Little to no preparations needed to guard against tropical tornadoes.", - "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + "action": "Ensure readiness for the next tropical tornado event.", }, "complete preparations": { - "planning": "Emergency planning for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "planning": "Emergency plans need not include a threat for tornadoes. Showers and thunderstorms with strong gusty winds may still occur.", "preparation": "Little to no preparations needed to guard against tropical tornadoes.", - "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + "action": "Ensure readiness for the next tropical tornado event.", }, "hunker down": { - "planning": "Emergency plans for this event need not include a threat for tornadoes. Showers and thunderstorms with strong wind gusts may still occur.", + "planning": "Emergency considerations need not include a threat for tornadoes. Showers and thunderstorms with strong gusty winds may still occur.", "preparation": "Little to no preparations needed to guard against tropical tornadoes.", - "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", + "action": "Ensure readiness for the next tropical tornado event.", }, "recovery": { - "planning": "Showers and thunderstorms with strong wind gusts may still occur.", - "preparation": "Exercise due safety when moving about.", + "planning": "Showers and thunderstorms with strong gusty winds may still occur.", + "preparation": "Exercise due safety.", "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", }, - "nothing to see here": { - "planning": "Showers and thunderstorms with strong wind gusts may occur.", - "preparation": "Exercise due safety when moving about.", + "default": { + "planning": "Showers and thunderstorms with strong gusty winds may still occur.", + "preparation": "Exercise due safety.", "action": "Review your seasonal plan and ensure readiness for the next tropical tornado event.", }, }, @@ -561,21 +559,21 @@ ThreatStatements = { } PotentialImpactStatements = { "Wind": { - "Extreme": ["Structural damage to sturdy buildings with some experiencing complete roof and wall failures. Complete destruction of mobile homes. Damage greatly accentuated by large airborne projectiles. Locations may be uninhabitable for weeks or months.", + "Extreme": ["Structural damage to sturdy buildings, some with complete roof and wall failures. Complete destruction of mobile homes. Damage greatly accentuated by large airborne projectiles. Locations may be uninhabitable for weeks or months.", "Numerous large trees snapped or uprooted along with fences and roadway signs blown over.", - "Many roads impassible from large debris, and more within urban or heavily wooded places. Many bridges, causeways, and access routes impassible.", + "Many roads impassable from large debris, and more within urban or heavily wooded places. Many bridges, causeways, and access routes impassable.", "Widespread power and communications outages."], - "High": ["Considerable roof damage to sturdy buildings, with some having window, door, and garage door failures leading to structural damage. Mobile homes severely damaged, with some destroyed. Damage accentuated by airborne projectiles. Locations may be uninhabitable for weeks.", + "High": ["Considerable roof damage to sturdy buildings, with some having window, door, and garage door failures leading to structural damage. Mobile homes severely damaged, with some destroyed. Damage accentuated by airborne projectiles. Locations may be uninhabitable for weeks.", "Many large trees snapped or uprooted along with fences and roadway signs blown over.", - "Some roads impassible from large debris, and more within urban or heavily wooded places. Several bridges, causeways, and access routes impassible.", + "Some roads impassable from large debris, and more within urban or heavily wooded places. Several bridges, causeways, and access routes impassable.", "Large areas with power and communications outages."], "Mod": ["Some damage to roofing and siding materials, along with damage to porches, awnings, carports, and sheds. A few buildings experiencing window, door, and garage door failures. Mobile homes damaged, especially if unanchored. Unsecured lightweight objects become dangerous projectiles.", "Several large trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted. Several fences and roadway signs blown over.", - "Some roads impassible from large debris, and more within urban or heavily wooded places. A few bridges, causeways, and access routes impassible.", + "Some roads impassable from large debris, and more within urban or heavily wooded places. A few bridges, causeways, and access routes impassable.", "Scattered power and communications outages, but more prevalent in areas with above ground lines."], "Elevated": ["Damage to porches, awnings, carports, sheds, and unanchored mobile homes. Unsecured lightweight objects blown about.", "Many large tree limbs broken off. A few trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted. Some fences and roadway signs blown over.", - "A few roads impassible from debris, particularly within urban or heavily wooded places. Hazardous driving conditions on bridges and other elevated roadways.", + "A few roads impassable from debris, particularly within urban or heavily wooded places. Hazardous driving conditions on bridges and other elevated roadways.", "Scattered power and communications outages."], "None": ["Little to no potential impacts from wind."], }, @@ -584,7 +582,7 @@ PotentialImpactStatements = { "Near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", "Extreme beach erosion. New shoreline cuts possible.", "Massive damage to marinas, docks, boardwalks, and piers. Numerous small craft broken away from moorings with many lifted onshore and stranded."], - "High": ["Large areas of deep inundation, with storm surge flooding accentuated by battering waves. Structural damage to buildings, with several washing away. Damage compounded by floating debris. Locations may be uninhabitable for an extended period.", + "High": ["Large areas of deep inundation with storm surge flooding accentuated by battering waves. Structural damage to buildings, with several washing away. Damage compounded by floating debris. Locations may be uninhabitable for an extended period.", "Large sections of near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", "Severe beach erosion with significant dune loss.", "Major damage to marinas, docks, boardwalks, and piers. Many small craft broken away from moorings, especially in unprotected anchorages with some lifted onshore and stranded."], @@ -601,20 +599,20 @@ PotentialImpactStatements = { "Flooding Rain": { "Extreme": ["Extreme rainfall flooding may prompt numerous evacuations and rescues.", "Rivers and tributaries may overwhelmingly overflow their banks in many places with deep moving water. Small streams, creeks, canals, arroyos, and ditches may become raging rivers. In mountain areas, deadly runoff may rage down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", - "Flood waters can enter numerous structures within multiple communities, with some structures becoming uninhabitable or washed away. Numerous places where flood waters may cover escape routes. Streets and parking lots become rivers of raging water with underpasses submerged. Driving conditions become very dangerous. Numerous road and bridge closures with some weakened or washed out."], + "Flood waters can enter numerous structures within multiple communities, some structures becoming uninhabitable or washed away. Numerous places where flood waters may cover escape routes. Streets and parking lots become rivers of raging water with underpasses submerged. Driving conditions become very dangerous. Numerous road and bridge closures with some weakened or washed out."], "High": ["Major rainfall flooding may prompt many evacuations and rescues.", "Rivers and tributaries may rapidly overflow their banks in multiple places. Small streams, creeks, canals, arroyos, and ditches may become dangerous rivers. In mountain areas, destructive runoff may run quickly down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", - "Flood waters can enter many structures within multiple communities, with some structures becoming uninhabitable or washed away. Many places where flood waters may cover escape routes. Streets and parking lots become rivers of moving water with underpasses submerged. Driving conditions become dangerous. Many road and bridge closures with some weakened or washed out."], + "Flood waters can enter many structures within multiple communities, some structures becoming uninhabitable or washed away. Many places where flood waters may cover escape routes. Streets and parking lots become rivers of moving water with underpasses submerged. Driving conditions become dangerous. Many road and bridge closures with some weakened or washed out."], "Mod": ["Moderate rainfall flooding may prompt several evacuations and rescues.", "Rivers and tributaries may quickly become swollen with swifter currents and overspill their banks in a few places, especially in usually vulnerable spots. Small streams, creeks, canals, arroyos, and ditches overflow.", "Flood waters can enter some structures or weaken foundations. Several places may experience expanded areas of rapid inundation at underpasses, low-lying spots, and poor drainage areas. Some streets and parking lots take on moving water as storm drains and retention ponds overflow. Driving conditions become hazardous. Some road and bridge closures."], "Elevated": ["Localized rainfall flooding may prompt a few evacuations.", "Rivers and tributaries may quickly rise with swifter currents. Small streams, creeks, canals, arroyos, and ditches may become swollen and overflow in spots.", - "Flood waters can enter a few structures, especially in usually vulnerable spots. A few places where rapid ponding of water occurs at underpasses, low-lying spots, and poor drainage areas. Several storm drains and retention ponds may become near-full and begin to overflow. Some brief road and bridge closures."], + "Flood waters can enter a few structures, especially in usually vulnerable spots. A few places where rapid ponding of water occurs at underpasses, low-lying spots, and poor drainage areas. Several storm drains and retention ponds become near-full and begin to overflow. Some brief road and bridge closures."], "None": ["Little to no potential impacts from flooding rain."], }, "Tornado": { - "Extreme": ["The occurrence of an outbreak of tornadoes can greatly hinder the execution of emergency plans during tropical events.", + "Extreme": ["The occurrence of an outbreak of tornadoes can greatly hinder the execution of emergency plans during tropical events. ", "Many places may experience tornado damage, with several spots of immense destruction, power loss, and communications failures.", "Locations could realize sturdy buildings demolished, structures upon weak foundations swept away, mobile homes obliterated, large trees twisted and snapped with some debarked, vehicles lifted off the ground and thrown with distance, and small boats destroyed. Large and deadly projectiles can add considerably to the toll."], "High": ["The occurrence of numerous tornadoes can greatly hinder the execution of emergency plans during tropical events.", @@ -630,9 +628,13 @@ PotentialImpactStatements = { }, } -EvacuationStatements = ["For those under evacuation orders, leave as soon as practical with a destination in mind. Gas up your vehicle well ahead of time. Be sure that you take essential materials from your Emergency Supplies Kit. Let others know where you are going and when you intend to arrive.", - "If evacuating the area, stick to prescribed evacuation routes. Look for additional traffic information on roadway smart signs and listen to select radio channels for further travel instructions. Do not use your cell phone while driving." - "For those not under evacuation orders, understand that there are inherent risks to evacuation (such as traffic congestion, accidents, and driving in bad weather), so evacuate only if necessary. Help keep roadways open for those that are under evacuation orders."] +EvacuationStatements = ["WATCH/WARNING PHASE - For those under evacuation orders, leave as soon as practical with a destination in mind. Gas up your vehicle well ahead of time. Be sure that you take all essential materials from your emergency supplies kit. Let others know where you are going and when you intend to arrive.", + "WATCH/WARNING PHASE - If evacuating the area, stick to prescribed evacuation routes. Look for additional traffic information on roadway smart signs and listen to select radio channels for further travel instructions. Drivers should not use cell phones while operating vehicles.", + "WATCH/WARNING PHASE - For those not under evacuation orders, understand that there are inherent risks to evacuation (such as traffic congestion, accidents, and driving in bad weather), so evacuate only if necessary. Help keep roadways open for those that are under evacuation orders.", + "WATCH/WARNING PHASE - If you are exceptionally vulnerable to wind or water hazards from tropical systems, consider voluntary evacuation, especially if being officially recommended. Relocate to a predetermined shelter or safe destination.", +"WATCH/WARNING PHASE - If evacuating away from the area or relocating to a nearby shelter, leave early before weather conditions become hazardous.", +"IMMINENT/ONGOING PHASE - Do not return to evacuated areas until hazardous winds diminish and flood waters abate.", +"RECOVERY PHASE - Do not return to evacuated areas until it is safe. Listen for the all-clear signal from local authorities."] OtherPreparednessActions = { @@ -650,7 +652,7 @@ OtherPreparednessActions = { "Outside preparations should be wrapped up as soon as possible before weather conditions completely deteriorate. Any remaining evacuations and relocations should be expedited before the onset of tropical storm force wind.", "If you are relocating to safe shelter, leave as early as possible. If heading to a community shelter, become familiar with the shelter rules before arrival, especially if you have special needs or own a pet. Take essential items with you from your Emergency Supplies Kit. Check the latest weather forecast before departing.", "Failure to adequately shelter may result in serious injury or loss of life. Always heed the advice of local officials and comply with any orders that are issued. Remember, during the storm 9 1 1 Emergency Services may not be able to immediately respond if conditions are unsafe. This should be a big factor in your decision making.", - "Check-in with your Emergency Points of Contact among family, friends, and workmates. Inform them of your status and well-being. Let them know how you intend to ride out the storm and when you plan to check-in again.", + "Check-in with your emergency points of contact among family, friends, and workmates. Inform them of your status and well-being. Let them know how you intend to ride out the storm and when you plan to check-in again.", "Keep cell phones well charged and handy. Also, cell phone chargers for automobiles can be helpful after the storm. Locate your chargers and keep them with your cell phone.", "In emergencies it is best to remain calm. Stay informed and focused on the situation at hand. Exercise patience with those you encounter. Be a Good Samaritan and helpful to others.", "If relocating to a nearby shelter or to the home of a family member or friend, drive with extra caution, especially on secondary roads. Remember, many bridges and causeways will be closed once higher winds arrive. Also, if you encounter water covering the road, seek an alternate route. Always obey official road signs for closures and detours.", @@ -662,20 +664,20 @@ OtherPreparednessActions = { "Do not venture outside while in the eye of a hurricane. Within the eye, weather conditions may temporarily improve which can be misleading. Once the eye passes, the wind will change direction and return to dangerous speeds. Heavy rain will also return. Be smart and remain safely hidden from the storm.", "Do not be a thrill seeker or risk your life for senseless photos or videos. Be wise and avoid becoming another statistic.", "Be ready to move to the identified safe room if your home or shelter begins to fail. Quickly move to an interior room on the lowest floor. Put as many sturdy walls between you and the storm as you can. Protect your head and body.", - "When major hurricanes make landfall, extreme winds bring a tremendous threat to life and cause devastating to catastrophic damage. The extent of these extreme winds is usually confined to locations near the coast and does not tend to cover an overly large area. Yet, this area will realize the brunt of the storm. At the onset of landfall, listen for extreme wind warnings. If issued for you area, move to the safest place within your home or shelter. Take the same life-saving actions as if it were a violent tornado."], + "When major hurricanes make landfall, extreme winds bring a tremendous threat to life and cause devastating to catastrophic damage. During landfall, listen for extreme wind warnings which indicate the exact timing and location of these incredible life-threatening winds. If issued for you area, move to the safest place within your home or shelter. Take the same life-saving actions as if it were a violent tornado."], "recovery": ["Remain safely sheltered until the storm fully passes. Once conditions improve, be careful going outside. Stay away from downed power lines and hazardous debris.", "If your home or shelter was damaged, be alert to the smell of natural gas leaks and cautious around exposed electrical wiring, broken glass, jagged metal and wood, and protruding nails and screws.", "Check to see if everyone in your group is OK. Administer first aid to those who are injured. Call 9 1 1 for any serious injuries. Remember, it may be more difficult for emergency responders to arrive quickly in the time period immediately following the storm.", - "Check-in with your Emergency Points of Contact. Let them know of your status and well-being. Keep conversations short and to the point. Do not tie up communications systems.", + "Check-in with your emergency points of contact. Let them know of your status and well-being. Keep conversations short and to the point. Do not tie up communications systems.", "Be a good neighbor and check on those living next to you. Be neighborly and lend a helping hand.", "Those who rode out the storm away from their home or business are likely anxious to return. However, allow some time for work crews to make a clear path for emergency vehicles. Downed power lines and trees may be blocking roads and flood waters may have washed out or overspread sections of key travel routes. Traffic lights may also be out of service.", "Do not attempt to return to evacuated areas until local authorities give the All-Clear signal.", - "Do not go sightseeing within impacted communities simply to observe storm damage. Sightseers can interfere with the timeliness of rescuers and first responders while needlessly jeopardizing their own lives and the lives of others.", + "Do not go sightseeing within impacted communities simply to observe storm damage. Sightseers can interfere with the timeliness of rescuers and first responders to needlessly jeopardize lives.", "When inspecting damage, use flashlights rather than candles or flamed lighting. Be aware of sparks that can ignite leaking gas or other flammables.", "Do not go up on your roof until the rain and strong winds have subsided. Ladders can be slippery in the rain and unexpected wind gusts can blow you off of the roof. Do not risk bodily harm in an attempt to reduce property damage.", "When clearing out fallen trees, be careful with chain saws and axes. Always wear protective gear and keep others at a safe distance. Use these tools according to operating manuals and safety instruction. Leaning trees and those which have fallen on roof tops can be especially challenging. If you are not in good health or unsure about what you are doing, have someone else with tree cutting experience do the job. Never cut trees without a partner.", "If using a generator, avoid carbon monoxide poisoning by following instructions by the manufacturer. Make sure that the generator is run in a well ventilated space.", - "Problems with sewer backups can contaminate standing flood waters. Keep children away. Also, listen for boil water alerts relative to communities whose tap water may have become temporarily non-potable."], + "Problems with sewer backups can further contaminate standing flood waters. Keep children away. Also, listen for boil water alerts relative to communities whose tap water may have become non-potable."], } AdditionalSources = ["- For information on appropriate preparations see ready.gov", diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py index 875e1de0a9..d64010c884 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/HLS.py @@ -1,4 +1,4 @@ -# Version 2015.1.6-0 +# Version 2015.2.12-1 import GenericHazards import string, time, os, re, types, copy, LogStream, collections @@ -9,7 +9,6 @@ import numpy import LocalizationSupport from AbsTime import * -from StartupDialog import IFPDialog as Dialog from com.raytheon.uf.common.dataplugin.gfe.reference import ReferenceData, ReferenceID from com.raytheon.uf.common.dataplugin.gfe.grid import Grid2DBit as JavaGrid2DBit AWIPS_ENVIRON = "AWIPS2" @@ -100,7 +99,6 @@ class TextProduct(HLSTCV_Common.TextProduct): "_getLowestThreat": 0, "_setHazardImpactCategories": 0, "_createWholeDomainEditArea": 0, - "_groupSegments": 0, "_determineHazards": 0, "_formatLocalTime": 0, "_getTimeZoneList": 0, @@ -129,13 +127,13 @@ class TextProduct(HLSTCV_Common.TextProduct): "_productHeader": 0, "_ugcHeader": 0, "_processProductParts": 0, - "_formatUGC_entries": 0, - "_getUgcInfo": 0, "_createProductDictionary": 0, "_initializeProductDictionary": 0, "_formatProductDictionary": 0, "_getStatValue": 0, "_allAreas": 0, + "_groupSegments": 0, + "_getSegmentVTECRecordsTuples": 0, "_computeIntersectAreas": 0, "_initializeHazardsTable": 0, "_getHazardsTable": 0, @@ -223,7 +221,7 @@ class TextProduct(HLSTCV_Common.TextProduct): } # Definition["debug"] = 1 # turn on ALL debug messages -# Definition["debug"] = 0 # turn off ALL debug messages + Definition["debug"] = 0 # turn off ALL debug messages def __init__(self): HLSTCV_Common.TextProduct.__init__(self) @@ -429,13 +427,16 @@ class TextProduct(HLSTCV_Common.TextProduct): if error is not None: return error - if self._stormName is None or self._stormName.strip() == "": + if self._stormName is None or self._stormName == "": return "Could not determine the storm name" self._loadLastTwoAdvisories() if self._previousAdvisory is None and self._ImpactsAnticipated: return "A TCV must be transmitted before an HLS can be run" + if len(self._IncludedImpacts) == 0: + return "At least one potential impact section needs to be included." + # Determine time ranges self._determineTimeRanges(argDict) @@ -452,7 +453,9 @@ class TextProduct(HLSTCV_Common.TextProduct): self._setHazardImpactCategories(threatName) # Create the product dictionary and format it to create the output - productDict = self._createProductDictionary(self._allAreas()) + productDict = self._createProductDictionary(self._productParts_HLS, + self._allAreas(), + areProductPartsSegmented=False) productOutput = self._formatProductDictionary(LegacyFormatter, productDict) return productOutput @@ -493,6 +496,7 @@ class TextProduct(HLSTCV_Common.TextProduct): statsDict['impactMin'] = None statsDict['impactMax'] = None statsDict['impactRange'] = None + statsDict['impactRangeMax'] = None self._samplingDict['WindThreat'] = copy.copy(statsDict) self._samplingDict['StormSurgeThreat'] = copy.copy(statsDict) @@ -553,35 +557,63 @@ class TextProduct(HLSTCV_Common.TextProduct): sectionDict['impactRange'] = "" sectionDict['impactLib'] = [] sectionDict['additionalImpactRange'] = [] - sectionDict['variedImpacts'] = False impactMin = self._samplingDict['WindThreat']['impactMin'] impactMax = self._samplingDict['WindThreat']['impactMax'] impactRange = self._samplingDict['WindThreat']['impactRange'] + impactRangeMax = self._samplingDict['WindThreat']['impactRangeMax'] inputThreatDominant = self._samplingDict['WindThreat']['inputThreatDominant'] # Test the simplest case first if impactMin == "none" and impactMax == "none": sectionDict['impactRange'] = impactRange - sectionDict['variedImpacts'] = None productDict['windSection'] = sectionDict return + qualifier = self._getImpactsQualifier(impactMax) + # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa_descriptor() + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for " + qualifier + "wind having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against " + qualifier + "wind having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from the main wind event are now unfolding across " + self._cwa_descriptor() + ". Remain well sheltered from " + qualifier + "wind having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + sectionDict['impactRange'] = "Little to no additional wind impacts expected." # Handle the case where the impacts are not the same across the entire CWA else: - sectionDict['variedImpacts'] = True - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage " + self._frame("ENTER AREA DESCRIPTION") + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for " + qualifier + "wind having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts in this area include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against " + qualifier + "wind having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts in this area include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from the main wind event are now unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well sheltered from " + qualifier + "wind having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + sectionDict['impactRange'] = "Little to no additional wind impacts expected." - sectionDict['impactLib'] = self._getPotentialImpactsStatements("Wind", self._impactCategoryToThreatLevel(impactMax)) + if self._GeneralOnsetTime != "recovery": + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Wind", self._impactCategoryToThreatLevel(impactMax)) + else: + sectionDict['impactLib'] = ["Community officials are now assessing the extent of actual wind impacts accordingly.", + "Emergency response teams are attending to casualty situations as needed.", + "Emergency work crews are restoring essential community infrastructure as necessary.", + "If you have an emergency dial 9 1 1.", + ] # If there are additional areas if impactRange != impactMax: + qualifier = self._getImpactsQualifier(impactRangeMax) - curPhrase = "Prepare for %s damage across %s." % \ - (impactRange, self._frame("ENTER AREA DESCRIPTION")) + if self._GeneralOnsetTime == "check plans": + curPhrase = "Also, prepare for " + qualifier + "wind having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "complete preparations": + curPhrase = "Also, protect against " + qualifier + "wind having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "hunker down": + curPhrase = "Potential impacts from the main wind event are also now unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well sheltered from " + qualifier + "wind having " + self._frame("possible | additional") + " " + impactRange + " impacts." + else: + curPhrase = "Little to no additional wind impacts expected." # If this phrase is not already part of the additional impacts if curPhrase not in sectionDict['additionalImpactRange']: @@ -614,12 +646,12 @@ class TextProduct(HLSTCV_Common.TextProduct): impactMin = self._samplingDict['StormSurgeThreat']['impactMin'] impactMax = self._samplingDict['StormSurgeThreat']['impactMax'] impactRange = self._samplingDict['StormSurgeThreat']['impactRange'] + impactRangeMax = self._samplingDict['StormSurgeThreat']['impactRangeMax'] inputThreatDominant = self._samplingDict['StormSurgeThreat']['inputThreatDominant'] # Test the simplest case first if impactMin == "none" and impactMax == "none": sectionDict['impactRange'] = impactRange - sectionDict['variedImpacts'] = None productDict['surgeSection'] = sectionDict return @@ -628,14 +660,27 @@ class TextProduct(HLSTCV_Common.TextProduct): lifeThreatening = "" if impactMax in ["significant", "extensive", "devastating", "catastrophic"]: - lifeThreatening = "life-threatening storm surge and " + lifeThreatening = "life-threatening " + elif impactMax == "limited": + lifeThreatening = "locally hazardous " - sectionDict['impactRange'] = "Prepare for " + \ - lifeThreatening + impactMax + \ - " damage in surge prone areas of " + self._cwa_descriptor() + ", with the greatest impacts " + \ - self._frame("ENTER AREA DESCRIPTION") + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for " + lifeThreatening + "surge having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts in this area include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against " + lifeThreatening + "surge having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts in this area include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from the main surge event are now unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well away from " + lifeThreatening + "surge having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + sectionDict['impactRange'] = "Little to no additional surge impacts expected." - sectionDict['impactLib'] = self._getPotentialImpactsStatements("Storm Surge", self._impactCategoryToThreatLevel(impactMax)) + if self._GeneralOnsetTime != "recovery": + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Storm Surge", self._impactCategoryToThreatLevel(impactMax)) + else: + sectionDict['impactLib'] = ["Community officials are now assessing the extent of actual surge impacts accordingly.", + "Emergency response teams are attending to casualty situations as needed.", + "Emergency work crews are restoring essential community infrastructure as necessary.", + "If you have an emergency dial 9 1 1.", + ] # Reexamine the impact range - we need to separate out "life-threatening" surge categories into a separate statement impactParts = impactRange.split(" ") @@ -649,9 +694,11 @@ class TextProduct(HLSTCV_Common.TextProduct): if impactParts[0] in ["limited", "none"]: # Make a new range to report impactRange = "significant" + impactRangeMax = impactRange if impactParts[2] != "significant": impactRange += " to " + impactParts[2] + impactRangeMax = impactParts[2] impactRangeRest = impactParts[0] @@ -665,8 +712,21 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional life-threatening surge areas if impactRange != impactMax and impactRange != impactMin: - curPhrase = "Brace for %s%s damage across %s." % \ - (lifeThreatening, impactRange, self._frame("ENTER AREA DESCRIPTION")) + lifeThreatening = "" + + if impactRangeMax in ["significant", "extensive", "devastating", "catastrophic"]: + lifeThreatening = "life-threatening " + elif impactRangeMax == "limited": + lifeThreatening = "locally hazardous " + + if self._GeneralOnsetTime == "check plans": + curPhrase = "Also, prepare for " + lifeThreatening + "surge having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "complete preparations": + curPhrase = "Also, protect against " + lifeThreatening + "surge having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "hunker down": + curPhrase = "Potential impacts from the main surge event are also now unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well away from " + lifeThreatening + "surge having " + self._frame("possible | additional") + " " + impactRange + " impacts." + else: + curPhrase = "Little to no additional surge impacts expected." self.debug_print("DEBUG: curPhrase = '%s'" % (curPhrase), 1) self.debug_print("DEBUG: sectionDict['additionalImpactRange'] = \n'%s'" % @@ -680,8 +740,16 @@ class TextProduct(HLSTCV_Common.TextProduct): # If there are additional areas if impactRangeRest != impactMax: - curPhrase = "Prepare for %s damage from storm surge across %s." % \ - (impactRangeRest, self._frame("ENTER AREA DESCRIPTION")) + lifeThreatening = "locally hazardous " + + if self._GeneralOnsetTime == "check plans": + curPhrase = "Also, prepare for " + lifeThreatening + "surge having possible " + impactRangeRest + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "complete preparations": + curPhrase = "Also, protect against " + lifeThreatening + "surge having possible " + impactRangeRest + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "hunker down": + curPhrase = "Potential impacts from the main surge event are also now unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well away from " + lifeThreatening + "surge having " + self._frame("possible | additional") + " " + impactRangeRest + " impacts." + else: + curPhrase = "Little to no additional surge impacts expected." # If this phrase is not already part of the additional impacts if curPhrase not in sectionDict['additionalImpactRange']: @@ -716,6 +784,7 @@ class TextProduct(HLSTCV_Common.TextProduct): impactMin = self._samplingDict['FloodingRainThreat']['impactMin'] impactMax = self._samplingDict['FloodingRainThreat']['impactMax'] impactRange = self._samplingDict['FloodingRainThreat']['impactRange'] + impactRangeMax = self._samplingDict['FloodingRainThreat']['impactRangeMax'] inputThreatDominant = self._samplingDict['FloodingRainThreat']['inputThreatDominant'] self.debug_print("In _floodingRainSection", 1) @@ -724,25 +793,68 @@ class TextProduct(HLSTCV_Common.TextProduct): # Test the simplest case first if impactMin == "none" and impactMax == "none": sectionDict['impactRange'] = impactRange - sectionDict['variedImpacts'] = None productDict['floodingRainSection'] = sectionDict return + qualifier = "" + if impactMax in ["extensive", "devastating", "catastrophic"]: + qualifier = "life-threatening " + elif impactMax == "significant": + qualifier = "dangerous " + elif impactMax == "limited": + qualifier = "locally hazardous " + # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding across " + self._cwa_descriptor() + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for " + qualifier + "rainfall flooding having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against " + qualifier + "rainfall flooding having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from the flooding rain are still unfolding across " + self._cwa_descriptor() + ". Remain well guarded against " + qualifier + "flood waters having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + sectionDict['impactRange'] = "Additional impacts from flooding rain are still a concern across " + self._cwa_descriptor() + ". Remain well guarded against " + qualifier + "flood waters having further impacts of " + impactMax + " potential." # Handle the case where the impacts are not the same across the entire CWA else: - sectionDict['variedImpacts'] = True - sectionDict['impactRange'] = "Prepare for " + impactMax + " flooding " + self._frame("ENTER AREA DESCRIPTION") + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for " + qualifier + "rainfall flooding having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against " + qualifier + "rainfall flooding having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from the flooding rain are still unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well guarded against " + qualifier + "flood waters having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + if impactMax != "none": + sectionDict['impactRange'] = "Additional impacts from flooding rain are still a concern across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well guarded against " + qualifier + "flood waters having further impacts of " + impactMax + " potential." + else: + sectionDict['impactRange'] = "Little to no additional impacts expected from flooding rain." - sectionDict['impactLib'] = self._getPotentialImpactsStatements("Flooding Rain", self._impactCategoryToThreatLevel(impactMax)) + if self._GeneralOnsetTime != "recovery": + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Flooding Rain", self._impactCategoryToThreatLevel(impactMax)) + else: + sectionDict['impactLib'] = [] # If there are additional areas if impactRange != impactMax: - curPhrase = "Prepare for %s flooding impacts across %s." % \ - (impactRange, self._frame("ENTER AREA DESCRIPTION")) + qualifier = "" + if impactRangeMax in ["extensive", "devastating", "catastrophic"]: + qualifier = "life-threatening " + elif impactRangeMax == "significant": + qualifier = "dangerous " + elif impactRangeMax == "limited": + qualifier = "locally hazardous " + + if self._GeneralOnsetTime == "check plans": + curPhrase = "Prepare for " + qualifier + "rainfall flooding having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "complete preparations": + curPhrase = "Protect against " + qualifier + "rainfall flooding having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "hunker down": + curPhrase = "Potential impacts from the flooding rain are still unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well guarded against " + qualifier + "flood waters having " + self._frame("possible | additional") + " " + impactRange + " impacts." + else: + if impactMax != "none": + curPhrase = "Additional impacts from flooding rain are still a concern across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well guarded against " + qualifier + "flood waters having further impacts of " + impactRange + " potential." + else: + curPhrase = "Little to no additional impacts expected from flooding rain." # If this phrase is not already part of the additional impacts if curPhrase not in sectionDict['additionalImpactRange']: @@ -775,12 +887,12 @@ class TextProduct(HLSTCV_Common.TextProduct): impactMin = self._samplingDict['TornadoThreat']['impactMin'] impactMax = self._samplingDict['TornadoThreat']['impactMax'] impactRange = self._samplingDict['TornadoThreat']['impactRange'] + impactRangeMax = self._samplingDict['TornadoThreat']['impactRangeMax'] inputThreatDominant = self._samplingDict['TornadoThreat']['inputThreatDominant'] # Test the simplest case first if impactMin == "none" and impactMax == "none": sectionDict['impactRange'] = impactRange - sectionDict['variedImpacts'] = None productDict['tornadoSection'] = sectionDict return @@ -791,6 +903,7 @@ class TextProduct(HLSTCV_Common.TextProduct): impactMin = "devastating" if impactRange in ["devastating", "catastrophic"]: impactRange = "devastating" + impactRangeMax = impactRange # If the max impact category is "catastrophic", and we lumped "devastating" in with it, ensure "devastating" is not # leftover as the high end of the range @@ -802,25 +915,67 @@ class TextProduct(HLSTCV_Common.TextProduct): if impactParts[0] != "extensive": # Force the upper end to be 1 category lower impactRange.replace("devastating", "extensive") + impactRangeMax = "extensive" # Otherwise, the impact is just "extensive" else: impactRange = "extensive" + impactRangeMax = "extensive" + + qualifier = "" + if impactMax in ["extensive", "devastating"]: + qualifier = "particularly dangerous " + elif impactMax == "significant": + qualifier = "dangerous " # If there is only one impact across the entire CWA, and it is the max if impactMax != "none" and impactMin == impactMax and inputThreatDominant != "None": - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage across " + self._cwa_descriptor() + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for a " + qualifier + "tornado event having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against a " + qualifier + "tornado event having possible " + impactMax + " impacts across " + self._cwa_descriptor() + ". Potential impacts include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from tornadoes are still unfolding across " + self._cwa_descriptor() + ". Remain well braced against a " + qualifier + "tornado event having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + sectionDict['impactRange'] = "Additional impacts from tornadoes are still a concern across " + self._cwa_descriptor() + ". Remain well braced against " + qualifier + "tornado event having further " + impactMax + " impact potential." # Handle the case where the impacts are not the same across the entire CWA else: - sectionDict['variedImpacts'] = True - sectionDict['impactRange'] = "Prepare for " + impactMax + " damage " + self._frame("ENTER AREA DESCRIPTION") + "." + if self._GeneralOnsetTime == "check plans": + sectionDict['impactRange'] = "Prepare for a " + qualifier + "tornado event having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts include:" + elif self._GeneralOnsetTime == "complete preparations": + sectionDict['impactRange'] = "Protect against a " + qualifier + "tornado event having possible " + impactMax + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + ". Potential impacts include:" + elif self._GeneralOnsetTime == "hunker down": + sectionDict['impactRange'] = "Potential impacts from tornadoes are still unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well braced against a " + qualifier + "tornado event having " + self._frame("possible | additional") + " " + impactMax + " impacts. If realized, these impacts include:" + else: + if impactMax != "none": + sectionDict['impactRange'] = "Additional impacts from tornadoes are still a concern across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well braced against " + qualifier + "tornado event having further " + impactMax + " impact potential." + else: + sectionDict['impactRange'] = "Little to no additional impacts expected from tornadoes." - sectionDict['impactLib'] = self._getPotentialImpactsStatements("Tornado", self._impactCategoryToThreatLevel(impactMax)) + if self._GeneralOnsetTime != "recovery": + sectionDict['impactLib'] = self._getPotentialImpactsStatements("Tornado", self._impactCategoryToThreatLevel(impactMax)) + else: + sectionDict['impactLib'] = [] # If there are additional areas if impactRange != impactMax: - curPhrase = "Prepare for %s damage across %s." % \ - (impactRange, self._frame("ENTER AREA DESCRIPTION")) + qualifier = "" + if impactRangeMax in ["extensive", "devastating"]: + qualifier = "particularly dangerous " + elif impactRangeMax == "significant": + qualifier = "dangerous " + + if self._GeneralOnsetTime == "check plans": + curPhrase = "Prepare for a " + qualifier + "tornado event having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "complete preparations": + curPhrase = "Protect against a " + qualifier + "tornado event having possible " + impactRange + " impacts across " + self._frame("ENTER AREA DESCRIPTION") + "." + elif self._GeneralOnsetTime == "hunker down": + curPhrase = "Potential impacts from tornadoes are still unfolding across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well braced against a " + qualifier + "tornado event having " + self._frame("possible | additional") + " " + impactRange + " impacts." + else: + if impactMax != "none": + curPhrase = "Additional impacts from tornadoes are still a concern across " + self._frame("ENTER AREA DESCRIPTION") + ". Remain well braced against " + qualifier + "tornado event having further " + impactRange + " impact potential." + else: + curPhrase = "Little to no additional impacts expected from tornadoes." # If this phrase is not already part of the additional impacts if curPhrase not in sectionDict['additionalImpactRange']: @@ -842,6 +997,17 @@ class TextProduct(HLSTCV_Common.TextProduct): productDict['tornadoSection'] = sectionDict + def _getImpactsQualifier(self, impact): + qualifier = "" + if impact in ["extensive", "devastating", "catastrophic"]: + qualifier = "life-threatening " + elif impact == "significant": + qualifier = "dangerous " + elif impact == "limited": + qualifier = "hazardous " + + return qualifier + def _coastalHazardsSection(self, productDict, productSegmentGroup, productSegment): productDict['coastalHazardsSection'] = self._frame("ENTER HERE A STATEMENT OF ANY ADDITIONAL HAZARDS OF CONCERN ALONG THE COAST SUCH AS RIP CURRENTS, HIGH WAVES, CONCERNS FOR BEACH EROSION ETC ETC IF NOT ALREADY DONE IN THE SURGE SECTION.") @@ -1199,7 +1365,7 @@ class TextProduct(HLSTCV_Common.TextProduct): # Determine dominant impact category for rest of CWA - No impact if impactMin == "none" and impactMax == "none": - impactRange = "No impacts are anticipated at this time across " + self._cwa_descriptor() + "." + impactRange = "Little to no " + self._frame("additional") + " impacts are anticipated at this time across " + self._cwa_descriptor() + "." # Otherwise, at least some impact will be experienced across the CWA else: # Do not permit the lowest category to be "None", if the highest category is also not "None" @@ -1210,6 +1376,7 @@ class TextProduct(HLSTCV_Common.TextProduct): if impactMin == impactMax: impactRange = impactMax + impactRangeMax = impactMax elif impactMin == impactRangeMax: impactRange = impactRangeMax else: @@ -1218,6 +1385,7 @@ class TextProduct(HLSTCV_Common.TextProduct): self._samplingDict[threatName]['impactMin'] = impactMin self._samplingDict[threatName]['impactMax'] = impactMax self._samplingDict[threatName]['impactRange'] = impactRange + self._samplingDict[threatName]['impactRangeMax'] = impactRangeMax ############################################################### ### Area, Zone and Segment related methods @@ -1234,30 +1402,6 @@ class TextProduct(HLSTCV_Common.TextProduct): refData = ReferenceData(gridLoc, refID, grid2Dbit) editAreaUtils.saveEditAreas([refData]) - def _groupSegments(self, segments): - ''' - Group the segments into the products - return a list of productSegmentGroup dictionaries - ''' - - segment_vtecRecords_tuples = [] - for segment in segments: - vtecRecords = self._getVtecRecords(segment) - self.debug_print("vtecRecords =\n\n%s\n" % (self._pp.pformat(vtecRecords))) - segment_vtecRecords_tuples.append((segment, vtecRecords)) - - productSegmentGroup = { - 'productID' : 'HLS', - 'productName': self._productName, - 'geoType': 'area', - 'vtecEngine': self._hazardsTable, - 'mapType': 'publicZones', - 'segmented': True, - 'productParts': self._productParts_HLS(segment_vtecRecords_tuples), - } - - return productSegmentGroup - ############################################################### ### Hazards related methods @@ -1393,7 +1537,7 @@ class TextProduct(HLSTCV_Common.TextProduct): headlineSearch[0] = re.sub("\.\.\.$", "", headlineSearch[0]) # # Remove the first and last '**' - if they exist -# headlineSearch[0] = headlineSearch[0].sub("**", "").strip() + headlineSearch[0] = headlineSearch[0].replace("**", "").strip() # Return the first cleaned-up headline string we found return self._cleanText(headlineSearch[0]) @@ -2321,7 +2465,7 @@ class LegacyFormatter(): elif name == "situationOverview": text += self.processSituationOverview(productDict['situationOverview']) elif name == "sigPotentialImpacts": - header = "Significant Potential Impacts" + header = "Potential Impacts" text += header + "\n" + "-"*len(header) + "\n\n" if not self._textProduct._ImpactsAnticipated: text += "None\n\n" @@ -2517,20 +2661,20 @@ class LegacyFormatter(): text = "* " + sectionDict['title'] + ":\n" impactRangeText = sectionDict['impactRange'] - if sectionDict['variedImpacts'] is not None: - if sectionDict['variedImpacts']: - impactRangeText += " In these areas, potential impacts include:" - else: - impactRangeText += " Potential impacts include:" - text += self._textProduct.indentText(impactRangeText, maxWidth=self._textProduct._lineLength) + if self._textProduct._GeneralOnsetTime == "recovery" and len(sectionDict['impactLib']) != 0: + text += "|*\n" + for impact in sectionDict['impactLib']: text += self._textProduct.indentText(impact, indentFirstString = self.TAB + "- ", indentNextString = self.TAB + " ", maxWidth=self._textProduct._lineLength) + if self._textProduct._GeneralOnsetTime == "recovery" and len(sectionDict['impactLib']) != 0: + text += "*|\n" + if len(sectionDict['additionalImpactRange']) != 0: text += "\n" diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py index ea032c7983..72d8cfc7d7 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/utility/edex_static/base/textproducts/templates/product/Hazard_TCV.py @@ -1,4 +1,4 @@ -# Version 2015.1.6-0 +# Version 2015.2.13-0 import GenericHazards import JsonSupport @@ -9,7 +9,6 @@ import math from AbsTime import * -from StartupDialog import IFPDialog as Dialog AWIPS_ENVIRON = "AWIPS2" import HLSTCV_Common @@ -78,8 +77,8 @@ class TextProduct(HLSTCV_Common.TextProduct): "_sampleData": 0, "_getStats": 0, "_determineSegments": 0, + "_getRefinedHazardSegments": 0, "_refineSegments": 0, - "_groupSegments": 0, "_makeSegmentEditAreas": 0, "_findSegment": 0, "_getAllVTECRecords": 0, @@ -101,13 +100,13 @@ class TextProduct(HLSTCV_Common.TextProduct): "_productHeader": 0, "_ugcHeader": 0, "_processProductParts": 0, - "_formatUGC_entries": 0, - "_getUgcInfo": 0, "_createProductDictionary": 0, "_initializeProductDictionary": 0, "_formatProductDictionary": 0, "_getStatValue": 0, "_allAreas": 0, + "_groupSegments": 0, + "_getSegmentVTECRecordsTuples": 0, "_computeIntersectAreas": 0, "_initializeHazardsTable": 0, "_getHazardsTable": 0, @@ -175,8 +174,10 @@ class TextProduct(HLSTCV_Common.TextProduct): "_isThreatDecreasing": 0, "_isThreatIncreasing": 0, "_advisoryHasValidKey": 0, - "_isMagnitudeIncreasing": 1, + "_isMagnitudeIncreasing": 0, "_calculateThreatStatementTr": 0, + "_pastWindHazardWasCAN": 0, + "_pastSurgeThreatsNotNone": 0, "_setThreatStatementsProductParts": 0, "_getThreatStatements": 0, "_potentialImpactsSummary": 0, @@ -193,7 +194,7 @@ class TextProduct(HLSTCV_Common.TextProduct): "_threatTrend": 0, "_threatStatements": 0, "_impactsSubsection": 0, - "_setStats": 1, + "_setStats": 0, #WindSection "_peakWind": 0, @@ -205,12 +206,15 @@ class TextProduct(HLSTCV_Common.TextProduct): #StormSurgeSection "_peakSurge": 0, - "_surgeWindow": 1, + "_surgeWindow": 0, #FloodingRainSection "_peakRain": 0, "_rainRange": 0, + #TornadoSection + "_tornadoSituation": 0, + #SectionCommonStats "_initializeSegmentAdvisories": 0, "_updateThreatStats": 0, @@ -218,10 +222,12 @@ class TextProduct(HLSTCV_Common.TextProduct): #WindSectionStats "_updateStatsForPwsXXint": 0, - "_updateStatsForPwsTXX": 1, + "_updateStatsForPwsTXX": 0, "_updateWindTimeInfo": 0, - "_computeWindOnsetAndEnd": 1, - "_createWindow": 1, + "_computeWindOnsetAndEnd": 0, + "_createWindow": 0, + "_calculateUTCandLocalHourOffset": 0, + "_isValidDayTime": 0, #Unique to each formatter, but common method name "execute": 0, @@ -261,7 +267,7 @@ class TextProduct(HLSTCV_Common.TextProduct): "makeUGCString": 0, "checkLastArrow": 0, } -# Definition["debug"] = 1 # turn on ALL debug messages +# Definition["debug"] = 1 # turn on ALL debug messages Definition["debug"] = 0 # turn off ALL debug messages @@ -376,17 +382,19 @@ class TextProduct(HLSTCV_Common.TextProduct): 'locationsAffected', 'fcstConfidence', (windSection, self._windSection[segment].sectionParts(segment_vtecRecords_tuple)), - (floodingRainSection, self._floodingRainSection[segment].sectionParts(segment_vtecRecords_tuple)), - (tornadoSection, self._tornadoSection[segment].sectionParts(segment_vtecRecords_tuple)), - 'infoSection', - 'endSection' ] - + # The storm surge section should never be inserted into # "inland" zones, since there will never be a surge impact. if segment not in self._inlandAreas(): - partsList.insert(9, + partsList.append( (stormSurgeSection, self._stormSurgeSection[segment].sectionParts(segment_vtecRecords_tuple))) + + partsList.extend([ + (floodingRainSection, self._floodingRainSection[segment].sectionParts(segment_vtecRecords_tuple)), + (tornadoSection, self._tornadoSection[segment].sectionParts(segment_vtecRecords_tuple)), + 'infoSection', + 'endSection']) return { 'arguments': segment_vtecRecords_tuple, @@ -401,11 +409,11 @@ class TextProduct(HLSTCV_Common.TextProduct): # Sample over 120 hours beginning at current time analysisList = [ # Wind Section - ("Wind", self.vectorModeratedMax, [6]), - ("WindGust", self.moderatedMax, [6]), + ("Wind", self.vectorModeratedMax, [3]), + ("WindGust", self.moderatedMax, [3]), ("WindThreat", self.mostSignificantDiscreteValue), - ("pws34int", self.moderatedMax, [6]), - ("pws64int", self.moderatedMax, [6]), + ("pws34int", self.moderatedMax, [3]), + ("pws64int", self.moderatedMax, [3]), ("pwsD34", self.moderatedMax), ("pwsN34", self.moderatedMax), ("pwsD64", self.moderatedMax), @@ -450,7 +458,7 @@ class TextProduct(HLSTCV_Common.TextProduct): if error is not None: return error - if self._stormName is None or self._stormName.strip() == "": + if self._stormName is None or self._stormName == "": return "Could not determine the storm name" self._segmentList = self._determineSegments() @@ -465,7 +473,9 @@ class TextProduct(HLSTCV_Common.TextProduct): self._sampleData(argDict) # Create the product dictionary and format it to create the output - productDict = self._createProductDictionary(self._segmentList) + productDict = self._createProductDictionary(self._productParts_TCV, + self._segmentList, + areProductPartsSegmented=True) productOutput = self._formatProductDictionary(LegacyFormatter, productDict) self._archiveCurrentAdvisory() @@ -507,54 +517,60 @@ class TextProduct(HLSTCV_Common.TextProduct): segment, vtecRecords = productSegment self.debug_print('setup_segment productSegment %s' % (self._pp.pformat(productSegment)), 1) # NOTE -- using _getVtecRecords to change to milliseconds - self._segmentVtecRecords = self._getVtecRecords(segment) + segmentVtecRecords = self._getVtecRecords(segment) # UGCs and Expire Time # Assume that the geoType is the same for all hazard events in the segment i.e. area or point self._ugcs = [segment] self._timeZones = self._tpc.hazardTimeZones(self._ugcs) - segmentDict['timeZones'] = self._timeZones - - for tz in self._timeZones: - if tz not in self._productTimeZones: - self._productTimeZones.append(tz) - self._purgeHours = self._purgeTime + + # In order to compute the expire time, the VTEC record times + # need to be in milliseconds. + recordsInMS = [] + for record in segmentVtecRecords: + recordInMS = copy.copy(record) + + recordInMS["startTime"] = recordInMS["startTime"] * 1000 + recordInMS["endTime"] = recordInMS["endTime"] * 1000 + if recordInMS.has_key("purgeTime"): + recordInMS["purgeTime"] = recordInMS["purgeTime"] * 1000 + if recordInMS.has_key("issueTime"): + recordInMS["issueTime"] = recordInMS["issueTime"] * 1000 + + recordsInMS.append(recordInMS) + + # Get the expire time in milliseconds since the epoch self._expireTime = self._tpc.getExpireTime( - self._issueTime_ms, self._purgeHours, self._segmentVtecRecords) + self._issueTime_ms, self._purgeHours, recordsInMS) + # Then convert it to a date segmentDict['expireTime'] = self._convertToISO(self._expireTime) - - # CAP Specific Fields - segmentDict['status'] = 'Actual' # Don't show UPG headlines nonUPGrecords = [] - for record in self._segmentVtecRecords: + for record in segmentVtecRecords: if record['act'] != "UPG": nonUPGrecords.append(record) - self._summaryHeadlines_value, self._headlines = self._tpc.getHeadlinesAndSections( + self._summaryHeadlines_value, _ = self._tpc.getHeadlinesAndSections( nonUPGrecords, self._productID, self._issueTime_secs) - + def _vtecRecords(self, segmentDict, productSegmentGroup, productSegment): - segment, vtecRecords = productSegment - records = [] - for vtecRecord in vtecRecords: - vstr = None - vstr = vtecRecord["vtecstr"] - - self.debug_print("vtecRecord = %s" % (self._pp.pformat(vtecRecord)), 1) - - # Post-process some VTEC codes which should not exist - vstr = vstr.replace(".EXT.", ".CON.") - vstr = vstr.replace(".EXB.", ".EXA.") - - if vtecRecord["phen"] == "SS": - # Temporary? Change the vtec mode for SS hazards to be experimental - vstr = vstr[0] + 'X' + vstr[2:] - records.append(vstr) - segmentDict['vtecRecords'] = records - + segment, vtecRecords = productSegment + records = [] + for vtecRecord in vtecRecords: + vstr = vtecRecord["vtecstr"] + + self.debug_print("vtecRecord = %s" % (self._pp.pformat(vtecRecord)), 1) + + if vtecRecord["phen"] == "SS": + # Temporary? Change the vtec mode for SS hazards to be experimental + vstr = vstr[0] + 'X' + vstr[2:] + + self.debug_print("final vstr = %s" % vstr, 1) + records.append(vstr) + segmentDict['vtecRecords'] = records + def _areaList(self, segmentDict, productSegmentGroup, productSegment): - # Area String + # Area String segmentDict['areaList'] = self._tpc.formatUGC_names(self._ugcs) def _issuanceTimeDate(self, segmentDict, productSegmentGroup, productSegment): @@ -562,13 +578,11 @@ class TextProduct(HLSTCV_Common.TextProduct): def _summaryHeadlines(self, segmentDict, productSegmentGroup, productSegment): segment, vtecRecords = productSegment - numRecords = len(vtecRecords) definitions = [] hazardsFound = [] for (phenSig, actions, name) in self.allowedHazards(): - for i in range(numRecords): - vtecRecord = vtecRecords[i] + for vtecRecord in vtecRecords: # The 'phensig' in the VTEC record could contain an # ETN. As such, we need to strip the ETN before doing a # comparison with the allowedHazards. @@ -598,7 +612,7 @@ class TextProduct(HLSTCV_Common.TextProduct): segmentDict['locationsAffected'] = [] if segment in tcv_AreaDictionary: - segmentDict['locationsAffected'] += tcv_AreaDictionary[segment]["locationsAffected"] + segmentDict['locationsAffected'] = tcv_AreaDictionary[segment]["locationsAffected"] def _fcstConfidence(self, segmentDict, productSegmentGroup, productSegment): # TODO - Get this from the TCM product potentially? Not included until provided from NHC @@ -622,26 +636,28 @@ class TextProduct(HLSTCV_Common.TextProduct): ################# Product Parts Helper Methods def _hazardDefinition(self, phenSig): - if phenSig == "HU.W": - return "A HURRICANE WARNING MEANS HURRICANE WIND CONDITIONS ARE " + \ - "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" - elif phenSig == "HU.A": - return "A HURRICANE WATCH MEANS HURRICANE WIND CONDITIONS ARE " + \ - "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" - if phenSig == "TR.W": - return "A TROPICAL STORM WARNING MEANS TROPICAL STORM WIND CONDITIONS ARE " + \ - "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" - elif phenSig == "TR.A": - return "A TROPICAL STORM WATCH MEANS TROPICAL STORM WIND CONDITIONS ARE " + \ - "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" - elif phenSig == "SS.W": - return "A STORM SURGE WARNING MEANS LIFE THREATENING INUNDATION LEVELS ARE " + \ - "EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" - elif phenSig == "SS.A": - return "A STORM SURGE WATCH MEANS LIFE THREATENING INUNDATION LEVELS ARE " + \ - "POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" + import VTECTable + + phen, sig = phenSig.split('.') + headline = VTECTable.VTECTable[phenSig]["hdln"] + + definition = "A " + headline + " MEANS " + + if phen == "HU": + definition += "HURRICANE WIND CONDITIONS" + elif phen == "TR": + definition += "TROPICAL STORM WIND CONDITIONS" + elif phen == "SS": + definition += "LIFE THREATENING INUNDATION LEVELS" else: return "" + + if sig == "W": # Warning + definition += " ARE EXPECTED SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 36 HOURS" + elif sig == "A": # Watch + definition += " ARE POSSIBLE SOMEWHERE WITHIN THIS AREA AND WITHIN THE NEXT 48 HOURS" + + return definition ############################################################### ### Sampling and Statistics related methods @@ -651,25 +667,46 @@ class TextProduct(HLSTCV_Common.TextProduct): def _sampleData(self, argDict): # Sample the data + self._createSamplers(argDict) + + # We need to preserve the ordering of the zones based off the zone combiner ordering + sortedAreas = sorted(self._allAreas(), + key=lambda x: self._segmentList.index(x) if x in self._segmentList else 9999) + for segment in sortedAreas: + self._initializeSegmentZoneData(segment) + + # We need stats for all zones to be saved in the advisory, + # regardless of whether or not it has a hazard in it. Getting + # the stats causes them to be added to the advisory. + windStats, stormSurgeStats, floodingRainStats, tornadoStats = \ + self._getStats(self._argDict, segment, self._editAreaDict, self._timeRangeList) + + # Only show zones with hazards in the output + if segment in self._segmentList: + # These segment sections will be added to the product parts + self._windSection[segment] = WindSection(self, segment, windStats) + self._stormSurgeSection[segment] = StormSurgeSection(self, segment, stormSurgeStats) + self._floodingRainSection[segment] = FloodingRainSection(self, segment, floodingRainStats) + self._tornadoSection[segment] = TornadoSection(self, segment, tornadoStats) + + def _createSamplers(self, argDict): + # Create the samplers used for sampling the data editAreas = self._makeSegmentEditAreas(argDict) - cwa = self._cwa() - editAreas.append((cwa, cwa)) self._sampler = self.getSampler(argDict, (self._analysisList(), self._timeRangeList, editAreas)) + # For storm surge, the edit areas are intersected with a special edit area intersectAreas = self._computeIntersectAreas(editAreas, argDict) - self._intersectSampler = self.getSampler(argDict, (self._intersectAnalysisList(), self._timeRangeList, intersectAreas)) - # Make a sample period for the previous rainfall self._previousRainfallTR = [(self._extraSampleTimeRange, "PrevRainfall")] self._extraRainfallSampler = self.getSampler(argDict, (self._extraRainfallAnalysisList(), self._previousRainfallTR, editAreas)) - + def _getStats(self, argDict, segment, editAreaDict, timeRangeList): # Get statistics for this segment @@ -680,11 +717,28 @@ class TextProduct(HLSTCV_Common.TextProduct): editArea) self.debug_print("*"*80, 1) - for index in range(len(timeRangeList)): - self.debug_print("editArea =" + editArea, 1) - self.debug_print("timeRangeList = %s" % (self._pp.pformat(timeRangeList[index])), 1) - self.debug_print("statList = %s" % (self._pp.pformat(statList[index])), 1) - self.debug_print("-"*40, 1) +# for index in range(len(timeRangeList)): + self.debug_print("editArea =" + editArea, 1) + self.debug_print("timeRangeList = %s" % (self._pp.pformat(timeRangeList)), 1) + self.debug_print("statList = %s" % (self._pp.pformat(statList)), 1) + self.debug_print("-"*40, 1) + + windStats = WindSectionStats(self, segment, statList, timeRangeList) + + # The surge section needs sampling done with an intersected edit area + if editArea in self._coastalAreas(): + intersectEditArea = "intersect_"+editArea + intersectStatList = self.getStatList(self._intersectSampler, + self._intersectAnalysisList(), + timeRangeList, + intersectEditArea) + else: + intersectStatList = "InlandArea" + + self.debug_print("intersectStatList = %s" % (self._pp.pformat(intersectStatList)), 1) + self.debug_print("-"*40, 1) + + stormSurgeStats = StormSurgeSectionStats(self, segment, intersectStatList, timeRangeList) # These stats are for handling the extra rainfall extraRainfallStatList = self.getStatList(self._extraRainfallSampler, @@ -692,20 +746,10 @@ class TextProduct(HLSTCV_Common.TextProduct): self._previousRainfallTR, editArea) - windStats = WindSectionStats(self, segment, statList, timeRangeList) floodingRainStats = FloodingRainSectionStats(self, segment, statList, timeRangeList, extraRainfallStatList, self._previousRainfallTR) tornadoStats = TornadoSectionStats(self, segment, statList, timeRangeList) - - # The surge section needs sampling done with an intersected edit area - intersectEditArea = "intersect_"+editArea - intersectStatList = self.getStatList(self._intersectSampler, - self._intersectAnalysisList(), - timeRangeList, - intersectEditArea) - - stormSurgeStats = StormSurgeSectionStats(self, segment, intersectStatList, timeRangeList) return (windStats, stormSurgeStats, floodingRainStats, tornadoStats) @@ -715,34 +759,52 @@ class TextProduct(HLSTCV_Common.TextProduct): def _determineSegments(self): # Get the segments based on hazards "overlaid" with combinations file - # Get the segments resulting from Hazards - self.debug_print("Raw Analyzed %s" % (self._pp.pformat(self._hazardsTable.rawAnalyzedTable())), 1) - hazSegments = self.organizeHazards(self._hazardsTable.rawAnalyzedTable()) - self.debug_print("Segments from HazardsTable organizeHazards %s" % (self._pp.pformat(hazSegments)), 1) - # Get the forecaster entered combinations accessor = ModuleAccessor.ModuleAccessor() self.debug_print("self._defaultEditAreas = %s" % (self._pp.pformat(self._defaultEditAreas)), 1) combos = accessor.variable(self._defaultEditAreas, "Combinations") + # combos is a list of tuples. Each tuple is a grouping of zones (a list of zones, combo name). if combos is None: LogStream.logVerbose("COMBINATION FILE NOT FOUND: " + self._defaultEditAreas) - return [], None + return [] self.debug_print("Segments from Zone Combiner = %s" % (self._pp.pformat(combos)), 1) + # "Overlay" the forecaster-entered combinations onto the segments - segmentList = self._refineSegments(hazSegments, combos) - self.debug_print("New segments = %s" % (self._pp.pformat(segmentList)), 1) + # so that the zones are ordered and grouped (as much as possible) + # as indicated in the zone combiner. + refinedHazardSegments = self._getRefinedHazardSegments(combos) # Instead of a segment being a group of zones, it will be just a single zone. # So collapse this list of lists down to a list of zones (aka. segments) segments = [] - for segment in segmentList: + for segment in refinedHazardSegments: segments += segment return segments + def _getRefinedHazardSegments(self, combos): + # Get a list of list of zones that are ordered and grouped + # based off of hazards and the provided zone combinations. + + # Get the raw analyzed table (a list of VTEC records) and organize the hazards + # to get a list of lists of zones that have the same hazards + self.debug_print("Raw Analyzed %s" % (self._pp.pformat(self._hazardsTable.rawAnalyzedTable())), 1) + hazSegments = self.organizeHazards(self._hazardsTable.rawAnalyzedTable()) + self.debug_print("Segments from HazardsTable organizeHazards %s" % (self._pp.pformat(hazSegments)), 1) + + # "Overlay" the forecaster-entered combinations onto the segments + # so that the zones are ordered and grouped (as much as possible) + # as indicated in the zone combiner. + refinedSegments = self._refineSegments(hazSegments, combos) + self.debug_print("New segments = %s" % (self._pp.pformat(refinedSegments)), 1) + + return refinedSegments + def _refineSegments(self, hazSegments, combos): - """Break down each segment further according to combos given. - Make sure the resulting segments follow the ordering of the combos. + """Reorder and regroup (as much as possible) the hazard segments + based off of the ordering and grouping in combos. Zones will + only be combined into groups if they share the same hazards + (regardless of whether they are grouped together in combos). """ if combos == []: return hazSegments @@ -759,6 +821,7 @@ class TextProduct(HLSTCV_Common.TextProduct): # to use) self._segmentList = hazSegments self.debug_print("self._segmentList = %s" % (self._pp.pformat(self._segmentList)), 1) + self.debug_print("current combo = %s" % (self._pp.pformat(combo)), 1) segmentMapping = map(self._findSegment, combo) self.debug_print(" segmentMapping = %s" % (self._pp.pformat(segmentMapping)), 1) @@ -782,6 +845,8 @@ class TextProduct(HLSTCV_Common.TextProduct): segAreas = segmentDict[key] newAreas = newAreas + segAreas newSegments.append(segAreas) + self.debug_print(" newAreas = %s" % (self._pp.pformat(newAreas)), 1) + self.debug_print(" newSegments = %s" % (self._pp.pformat(newSegments)), 1) self.debug_print(" newSegments = %s" % (self._pp.pformat(newSegments)), 1) # Now add in the hazAreas that have not been accounted for # in the combinations @@ -792,50 +857,11 @@ class TextProduct(HLSTCV_Common.TextProduct): newSeg.append(hazArea) if newSeg != []: newSegments.append(newSeg) + self.debug_print(" final newSegments = %s" % (self._pp.pformat(newSegments)), 1) return newSegments - def _groupSegments(self, segmentsWithHazards): - ''' - Group the segments into the products - return a list of productSegmentGroup dictionaries - ''' - - segment_vtecRecords_tuples = [] - - # We need to preserve the ordering of the zones based off the zone combiner ordering - sortedAreas = sorted(self._allAreas(), - key=lambda x: segmentsWithHazards.index(x) if x in segmentsWithHazards else 9999) - for segment in sortedAreas: - self._initializeSegmentZoneData(segment) - - # We need stats for all zones to be saved in the advisory, - # regardless of whether or not it has a hazard in it - windStats, stormSurgeStats, floodingRainStats, tornadoStats = \ - self._getStats(self._argDict, segment, self._editAreaDict, self._timeRangeList) - - # Only show zones with hazards in the output - if segment in segmentsWithHazards: - vtecRecords = self._getVtecRecords(segment) - segment_vtecRecords_tuples.append((segment, vtecRecords)) - - self._windSection[segment] = WindSection(self, segment, windStats) - self._stormSurgeSection[segment] = StormSurgeSection(self, segment, stormSurgeStats) - self._floodingRainSection[segment] = FloodingRainSection(self, segment, floodingRainStats) - self._tornadoSection[segment] = TornadoSection(self, segment, tornadoStats) - - productSegmentGroup = { - 'productID' : 'TCV', - 'productName': self._productName, - 'geoType': 'area', - 'vtecEngine': self._hazardsTable, - 'mapType': 'publicZones', - 'segmented': True, - 'productParts': self._productParts_TCV(segment_vtecRecords_tuples), - } - - return productSegmentGroup - def _makeSegmentEditAreas(self, argDict): + # Create the edit areas that will be sampled areasList = self._allAreas() self.debug_print("areasList = %s" % (self._pp.pformat(areasList)), 1) editAreas = [] @@ -846,6 +872,7 @@ class TextProduct(HLSTCV_Common.TextProduct): return editAreas def _findSegment(self, areaName): + # Determine which hazard group a zone belongs to for segment in self._segmentList: if areaName in segment: return segment @@ -856,34 +883,44 @@ class TextProduct(HLSTCV_Common.TextProduct): def _getAllVTECRecords(self): allRecords = [] + # Only the segments in _segmentList contain hazards so no + # need to check everything in _allAreas() for segment in self._segmentList: allRecords += self._getVtecRecords(segment) return allRecords def _getHazardsForHLS(self): + # Get all the hazards so that the HLS will have access to them. + # Areas that share the same hazards are grouped together + # into a single hazard. hazardTable = self._argDict["hazards"] - - hazSegments = self.organizeHazards(hazardTable.rawAnalyzedTable()) - self.debug_print("Segments from HazardsTable organizeHazards %s" % - (self._pp.pformat(hazSegments)), 1) - + # Create a single grouping of all zones. This will make it so that + # the hazards are grouped together as much as possible so that we + # don't repeat hazard information for zones in HLS. combos = [([self._allAreas()], "AllAreas")] - self.debug_print("Segments from Zone Combiner %s" % (self._pp.pformat(combos)), 1) - # "Overlay" the forecaster-entered combinations onto the segments - segmentList = self._refineSegments(hazSegments, combos) - self.debug_print("SegmentList from refineSegments = %s" % - (self._pp.pformat(segmentList)), 1) + # "Overlay" this group of all zones onto the segments + # so that we get as few groups of zones as possible. + refinedHazardSegments = self._getRefinedHazardSegments(combos) allHazards = [] - for segment in segmentList: + for segment in refinedHazardSegments: hazardsList = hazardTable.getHazardList(segment) for hazard in hazardsList: + # If this is a correction, don't generate new hazards, + # use the previous ones if hazard['act'] == 'COR': return self._previousAdvisory["HazardsForHLS"] else: + # Tropical hazards shouldn't ever have EXT and EXB actions since + # they are "until further notice" + if hazard["act"] == "EXT": + hazard["act"] = "CON" + elif hazard["act"] == "EXB": + hazard["act"] = "EXA" + allHazards.append(hazard) return allHazards @@ -892,6 +929,7 @@ class TextProduct(HLSTCV_Common.TextProduct): ### Time related methods def _convertToISO(self, time_ms, local=None): + # Convert milliseconds since the epoch to a date import datetime dt = datetime.datetime.fromtimestamp(time_ms / 1000) if local: @@ -908,7 +946,7 @@ class TextProduct(HLSTCV_Common.TextProduct): ### Advisory related methods def _initializeSegmentZoneData(self, segment): - # The current advisory will be populated when setting a section's stats + # The current advisory will be populated when getting a section's stats self._currentAdvisory['ZoneData'][segment] = { "WindThreat": None, "WindForecast": None, @@ -919,6 +957,37 @@ class TextProduct(HLSTCV_Common.TextProduct): "TornadoThreat": None, } + def _getPreviousAdvisories(self): + stormAdvisories = self._getStormAdvisoryNames() + + self.debug_print("DEBUG: stormAdvisories = %s" % + (self._pp.pformat(stormAdvisories)), 1) + + previousAdvisories = [] + + # Get the current storm name from the TCP + curAdvisoryString = self._getStormNameFromTCP() + self.debug_print("DEBUG: curAdvisoryString = %s" % + (curAdvisoryString), 1) + + # Filter out the advisories we wish to process further + for advisory in stormAdvisories: + + # If this was an advisory for the current storm + if advisory.find(curAdvisoryString) != -1: + + # Load this advisory for this storm + curAdvisory = self._loadAdvisory(advisory) + + if curAdvisory is not None: + previousAdvisories.append(curAdvisory) + + + self.debug_print("DEBUG: previous advisories = %s" % + (self._pp.pformat(previousAdvisories)), 1) + + return previousAdvisories + def _archiveCurrentAdvisory(self): ### Determine if all actions are canceled allCAN = True @@ -935,41 +1004,27 @@ class TextProduct(HLSTCV_Common.TextProduct): self._currentAdvisory["AdvisoryNumber"] = self._getAdvisoryNumberStringFromTCP() self._currentAdvisory["HazardsForHLS"] = self._getHazardsForHLS() - self._saveAdvisory("pending", self._currentAdvisory) def _saveAdvisory(self, advisoryName, advisoryDict): self._synchronizeAdvisories() fileName = self._getAdvisoryFilename(advisoryName) -# print "*"*80 -# print "advisoryDict = ", advisoryDict -# print "-"*80 + "\n" -# print type(advisoryDict["HazardsForHLS"]) - newList = [] - for item in advisoryDict["HazardsForHLS"]: -# print item, "\n" - - # Now handle the action code we should not have - if item["act"] == "EXT": - item["act"] = "CON" - elif item["act"] == "EXB": - item["act"] = "EXA" - - newList.append(item) - advisoryDict["HazardsForHLS"] = newList + self.debug_print("Saving %s to %s" % (advisoryName, fileName), 1) + self.debug_print("advisoryDict: %s" % (self._pp.pformat(advisoryDict)), 1) try: JsonSupport.saveToJson(LocalizationSupport.CAVE_STATIC, self._site, fileName, advisoryDict) - - self.debug_print("Wrote file contents for: %s" % (fileName), 1) - - self._synchronizeAdvisories() except Exception, e: self.debug_print("Save Exception for %s : %s" % (fileName, e), 1) + else: # No exceptions occurred + self.debug_print("Wrote file contents for: %s" % (fileName), 1) + + # Purposely allow this to throw + self._synchronizeAdvisories() ############################################################### ### GUI related methods @@ -995,7 +1050,7 @@ class TextProduct(HLSTCV_Common.TextProduct): "default": "None", }, ] - + def _displayGUI(self, infoDict=None): dialog = Overview_Dialog(self, "TCV", infoDict) status = dialog.status() @@ -1100,7 +1155,7 @@ class Overview_Dialog(HLSTCV_Common.Common_Dialog): if entryField is not None: entryName = self._entryName(name) - self._setVarDict(entryName, tkObject_dict[entryName].get()) + self._setVarDict(entryName, tkObject_dict[entryName].get()) # close window and set status "Ok" self._status = "Ok" self.withdraw() @@ -1112,7 +1167,18 @@ class SectionCommon(): self._textProduct = textProduct self._sectionHeaderName = sectionHeaderName self._segment = segment + self._tr = None + self.isThreatInAllAdvisories = False + def _isThreatInAllAdvisories(self, threatName): + previousAdvisories = self._textProduct._getPreviousAdvisories() + + for advisory in previousAdvisories: + if advisory["ZoneData"][self._segment][threatName] == "None": + return False + + return True + def _setProductPartValue(self, dictionary, productPartName, value): dictionary[self._sectionName + '._' + productPartName] = value @@ -1139,7 +1205,7 @@ class SectionCommon(): threatLevel = "Moderate" self._setProductPartValue(segmentDict, 'lifePropertyThreatSummary', - "Threat to Life and Property: " + threatLevel) + "Current Threat to Life and Property: " + threatLevel) # This new method will convert the single word threat trend into # an appropriate sentence @@ -1273,30 +1339,79 @@ class SectionCommon(): self._textProduct.debug_print("the current advisory and/or previous advisory did not have key: %s" % (forecastKey), 1) return False - def _calculateThreatStatementTr(self, onsetHour, endHour, threatTrendValue): - tr = None + def _calculateThreatStatementTr(self, onsetHour, endHour, section): + tr = "default" self._textProduct.debug_print("onset hour = %s" % (onsetHour), 1) self._textProduct.debug_print("end hour = %s" % (endHour), 1) - self._textProduct.debug_print("threatTrendValue = %s" % - (threatTrendValue), 1) - if (onsetHour is not None) and \ - (endHour is not None): - + if (onsetHour is not None): if onsetHour > 36: tr = "check plans" elif onsetHour > 6: tr = "complete preparations" - elif onsetHour <= 6 and endHour > 0: + elif (onsetHour <= 6) and (endHour is not None) and (endHour > 0): tr = "hunker down" - elif (threatTrendValue is not None) and (threatTrendValue.upper() in ["DECREASING", "NEARLY STEADY"]): - tr = "recovery" - else: - tr = "nothing to see here" - + + self._textProduct.debug_print("tr is currently -> '%s'" % (tr), 1) + + if tr == "default": + records = self._textProduct._getVtecRecords(self._segment) + for record in records: + if record["phen"] in ["HU", "TR"] and record["sig"] == "W": + if record["act"] == "CAN": + tr = "recovery" + break + # This is just for 2015 + elif record["act"] == "CON" and \ + section == "Surge" and \ + self._textProduct._currentAdvisory['ZoneData'][self._segment]["StormSurgeThreat"] == "None" and \ + self._pastSurgeThreatsNotNone(): + tr = "recovery" + break + + if tr == "default" and \ + section == "Wind" and \ + self._pastWindHazardWasCAN(): + tr = "recovery" + return tr + def _pastWindHazardWasCAN(self): + previousAdvisories = self._textProduct._getPreviousAdvisories() + + # If there are NOT any advisories to process - no need to continue + if len(previousAdvisories) == 0: + return False + + # Look at all past advisories for this storm + for advisory in previousAdvisories: + + for hazard in advisory["HazardsForHLS"]: + if self._segment in hazard["id"] and \ + hazard["phen"] in ["TR", "HU"] and \ + hazard["sig"] == "W" and \ + hazard["act"] == "CAN": + return True + + return False + + def _pastSurgeThreatsNotNone(self): + previousAdvisories = self._textProduct._getPreviousAdvisories() + + # If there are NOT any advisories to process - no need to continue + if len(previousAdvisories) == 0: + return False + + # Look at all past advisories for this storm + for advisory in previousAdvisories: + + # We had a threat previously + if advisory["ZoneData"][self._segment]["StormSurgeThreat"] in ["Elevated", "Mod", "High", "Extreme"]: + return True + + return False + def _setThreatStatementsProductParts(self, segmentDict, productSegment, tr): self._textProduct.debug_print("tr = %s %s" % @@ -1321,7 +1436,7 @@ class SectionCommon(): with open("/awips2/cave/etc/gfe/userPython/utilities/TCVDictionary.py", 'r') as pythonFile: fileContents = pythonFile.read() exec(fileContents) - + # ThreatStatements comes from TCVDictionary.py when it is exec'ed threatStatements = ThreatStatements @@ -1345,6 +1460,12 @@ class SectionCommon(): self._setProductPartValue(segmentDict, 'potentialImpactsSummary', summary) def _getPotentialImpactsSummaryText(self, maxThreat): + if self._tr is not None: + if self._tr == "hunker down": + return "Potential Impacts: Still Unfolding" + elif self._tr == "recovery": + return "Realized Impacts: Still Being Assessed" + if maxThreat == "Extreme": impactLevel = "Devastating to Catastrophic" elif maxThreat == "High": @@ -1366,8 +1487,18 @@ class SectionCommon(): self._setProductPartValue(segmentDict, 'potentialImpactsStatements', statements) def _getPotentialImpactsStatements(self, productSegment, elementName, maxThreat): - import TCVDictionary + if self._tr is not None: + specialStatements = self._specialImpactsStatements() + if self._tr in specialStatements.keys(): + if self._tr == "default": + return specialStatements[self._tr][maxThreat] + else: + if self._tr == "recovery" and not self.isThreatInAllAdvisories: + return [] + else: + return specialStatements[self._tr] + import TCVDictionary potentialImpactStatements = TCVDictionary.PotentialImpactStatements statements = potentialImpactStatements[elementName][maxThreat] @@ -1390,6 +1521,10 @@ class SectionCommon(): return statements + # Specific hazard sections can override this to provide special impacts statements + def _specialImpactsStatements(self): + return {} + def _preparationStatement(self, severityString): preparationStatement = "" if severityString == "DEVASTATING" or severityString == "EXTENSIVE IMPACTS": @@ -1415,6 +1550,7 @@ class WindSection(SectionCommon): SectionCommon.__init__(self, textProduct, segment, "Wind") self._sectionName = 'windSection[\'' + segment + '\']' self._stats = stats + self.isThreatInAllAdvisories = self._isThreatInAllAdvisories("WindThreat") def sectionParts(self, segment_vtecRecords_tuple): parts = [ @@ -1512,57 +1648,69 @@ class WindSection(SectionCommon): self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): - self._threatTrendValue = \ + threatTrendValue = \ self._getThreatTrendValue("Wind", magnitudeIncreaseThreshold=self._textProduct.mphToKt(15)) - if self._threatTrendValue is not None: + if threatTrendValue is not None: # Convert the threat trend to a sentence threatTrendSentence = \ - self._getThreatTrendSentence("wind", self._threatTrendValue) + self._getThreatTrendSentence("wind", threatTrendValue) self._setProductPartValue(segmentDict, 'threatTrend', threatTrendSentence) def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): - windTr = self._calculateThreatStatementTr(self._stats._onset34Hour, - self._stats._end34Hour, - self._threatTrendValue) + self._tr = self._calculateThreatStatementTr(self._stats._onset34Hour, + self._stats._end34Hour, "Wind") self._textProduct.debug_print("in _threatStatements tr = %s" % - (self._textProduct._pp.pformat(windTr)), 1) + (self._textProduct._pp.pformat(self._tr)), 1) if not hasattr(self._textProduct, "_windThreatStatementsTr"): self._textProduct._windThreatStatementsTr = dict() - self._textProduct._windThreatStatementsTr[self._segment] = windTr + self._textProduct._windThreatStatementsTr[self._segment] = self._tr self._setThreatStatementsProductParts(segmentDict, productSegment, - windTr) - -# def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): -# subsectionDict = collections.OrderedDict() -# self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) -# self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) -# if len(subsectionDict) > 0: -# self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + self._tr) - # Modified to not include wind impacts during the "recovery" and - # "nothing to see here" phases of the tropical cyclone event def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): - - # Compute time range to onset - try: - windTr = self._textProduct._windThreatStatementsTr[self._segment] - except: - windTr = self._calculateThreatStatementTr(self._stats._onset34Hour, - self._stats._end34Hour, - self._threatTrendValue) - subsectionDict = collections.OrderedDict() self._potentialImpactsSummary(subsectionDict, productSegmentGroup, productSegment) self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) if len(subsectionDict) > 0: self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + def _specialImpactsStatements(self): + return {"hunker down": ["Potential impacts from the main wind event are still unfolding.", + "The extent of realized impacts will depend on the actual strength, duration, and exposure of the wind as experienced at particular locations.", + ], + "recovery": ["Little to no additional wind impacts expected. Community officials are now assessing the extent of actual wind impacts accordingly.", + ], + "default": {"Extreme": ["If realized, major hurricane force wind can cause structural damage to sturdy buildings, some with complete roof and wall failures. Complete destruction of mobile homes. Damage greatly accentuated by large airborne projectiles. Locations may be uninhabitable for weeks or months.", + "Numerous large trees snapped or uprooted along with fences and roadway signs blown over.", + "Many roads impassable from large debris, and more within urban or heavily wooded places. Many bridges, causeways, and access routes impassable.", + "Widespread power and communication outages.", + ], + "High": ["If realized, hurricane force wind can cause considerable roof damage to sturdy buildings, with some having window, door, and garage door failures leading to structural damage. Mobile homes severely damaged, with some destroyed. Damage accentuated by airborne projectiles. Locations may be uninhabitable for weeks.", + "Many large trees snapped or uprooted along with fences and roadway signs blown over.", + "Some roads impassable from large debris, and more within urban or heavily wooded places. Several bridges, causeways, and access routes impassable.", + "Large areas with power and communications outages.", + ], + "Mod": ["If realized, strong tropical storm force wind can cause some damage to roofing and siding materials, along with damage to porches, awnings, carports, and sheds. A few buildings experiencing window, door, and garage door failures. Mobile homes damaged, especially if unanchored. Unsecured lightweight objects become dangerous projectiles.", + "Several large trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted. Several fences and roadway signs blown over.", + "Some roads impassable from large debris, and more within urban or heavily wooded places. A few bridges, causeways, and access routes connecting barrier islands impassable.", + "Scattered power and communications outages, but more prevalent in areas with above ground lines.", + ], + "Elevated": ["If realized, tropical storm force wind can cause damage to porches, awnings, carports, sheds, and unanchored mobile homes. Unsecured lightweight objects blown about.", + "Many large tree limbs broken off. A few trees snapped or uprooted, but with greater numbers in places where trees are shallow rooted. Some fences and roadway signs blown over.", + "A few roads impassable from debris, particularly within urban or heavily wooded places. Hazardous driving conditions on bridges and other elevated roadways.", + "Scattered power and communications outages.", + ], + "None": ["Little to no potential impacts from wind.", + ] + } + } ### Supporting functions def _moderatedMaxWindMph_categories(self): @@ -1598,6 +1746,7 @@ class StormSurgeSection(SectionCommon): SectionCommon.__init__(self, textProduct, segment, "Storm Surge") self._sectionName = 'stormSurgeSection[\'' + segment + '\']' self._stats = stats + self.isThreatInAllAdvisories = self._isThreatInAllAdvisories("StormSurgeThreat") def sectionParts(self, segment_vtecRecords_tuple): parts = [ @@ -1696,24 +1845,23 @@ class StormSurgeSection(SectionCommon): SectionCommon._lifePropertyThreatSummary(self, segmentDict, productSegmentGroup, productSegment) def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): - self._threatTrendValue = self._getThreatTrendValue("StormSurge", magnitudeIncreaseThreshold=4) + threatTrendValue = self._getThreatTrendValue("StormSurge", magnitudeIncreaseThreshold=4) - if self._threatTrendValue is not None: + if threatTrendValue is not None: # Convert the threat trend to a sentence threatTrendSentence = \ - self._getThreatTrendSentence("storm surge", self._threatTrendValue) + self._getThreatTrendSentence("storm surge", threatTrendValue) self._setProductPartValue(segmentDict, 'threatTrend', threatTrendSentence) def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): self._textProduct.debug_print("Surge Threat Statements", 1) - surgeTr = self._calculateThreatStatementTr(self._stats._onsetSurgeHour, - self._stats._endSurgeHour, - self._threatTrendValue) + self._tr = self._calculateThreatStatementTr(self._stats._onsetSurgeHour, + self._stats._endSurgeHour, "Surge") self._setThreatStatementsProductParts(segmentDict, productSegment, - surgeTr) + self._tr) def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() @@ -1724,6 +1872,37 @@ class StormSurgeSection(SectionCommon): if len(subsectionDict) > 0: self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + def _specialImpactsStatements(self): + return {"hunker down": ["Potential impacts from the main surge event are still unfolding.", + "The extent of realized impacts will depend on the actual height of storm surge moving onshore and the resulting depth of coastal flooding as experienced at particular locations.", + ], + "recovery": ["Little to no additional surge impacts expected. Community officials are now assessing the extent of actual surge impacts accordingly.", + ], + "default": {"Extreme": ["If realized, extreme storm surge flooding can cause widespread deep inundation accentuated by powerful battering waves. Structural damage to buildings, with many washing away. Damage greatly compounded from considerable floating debris. Locations may be uninhabitable for an extended period.", + "Near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", + "Extreme beach erosion. New shoreline cuts possible.", + "Massive damage to marinas, docks, boardwalks, and piers. Numerous small craft broken away from moorings with many lifted onshore and stranded.", + ], + "High": ["If realized, major storm surge flooding can cause large areas of deep inundation accentuated by battering waves. Structural damage to buildings, with several washing away. Damage compounded by floating debris. Locations may be uninhabitable for an extended period.", + "Large sections of near-shore escape routes and secondary roads washed out or severely flooded. Flood control systems and barriers may become stressed.", + "Severe beach erosion with significant dune loss.", + "Major damage to marinas, docks, boardwalks, and piers. Many small craft broken away from moorings, especially in unprotected anchorages with some lifted onshore and stranded.", + ], + "Mod": ["If realized, moderate storm surge flooding can cause areas of inundation accentuated by large waves. Damage to several buildings, mainly near the coast.", + "Sections of near-shore escape routes and secondary roads become weakened or washed out, especially in usually vulnerable low spots.", + "Major beach erosion with heavy surf breaching dunes. Strong and numerous rip currents.", + "Moderate damage to marinas, docks, boardwalks, and piers. Several small craft broken away from moorings, especially in unprotected anchorages.", + ], + "Elevated": ["If realized, minor to moderate storm surge flooding can cause localized inundation mainly along immediate shorelines and in low-lying spots, or in areas farther inland near where higher surge waters move ashore.", + "Sections of near-shore roads and parking lots become overspread with surge water. Driving conditions dangerous in places where surge water covers the road.", + "Moderate beach erosion. Heavy surf also breaching dunes, mainly in usually vulnerable locations. Strong rip currents.", + "Minor to locally moderate damage to marinas, docks, boardwalks, and piers. A few small craft broken away from moorings.", + ], + "None": ["Little to no potential impacts from storm surge.", + ] + } + } + def _potentialImpactsSummary(self, segmentDict, productSegmentGroup, productSegment): if not self._textProduct._PopulateSurge: @@ -1741,6 +1920,7 @@ class FloodingRainSection(SectionCommon): SectionCommon.__init__(self, textProduct, segment, "Flooding Rain") self._sectionName = 'floodingRainSection[\'' + segment + '\']' self._stats = stats + self.isThreatInAllAdvisories = self._isThreatInAllAdvisories("FloodingRainThreat") def sectionParts(self, segment_vtecRecords_tuple): parts = [ @@ -1784,7 +1964,7 @@ class FloodingRainSection(SectionCommon): def _peakRain(self, segmentDict, productSegmentGroup, productSegment): if self._stats._sumAccum is not None: - words = self._rainRange(int(math.ceil(self._stats._sumAccum))) + words = self._rainRange(int(self._stats._sumAccum + 0.5)) # If we have previous rainfall if self._stats._prevAccum not in [0.0, None]: @@ -1831,20 +2011,20 @@ class FloodingRainSection(SectionCommon): self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): - self._threatTrendValue = self._getThreatTrendValue("FloodingRain", magnitudeIncreaseThreshold=4) + threatTrendValue = self._getThreatTrendValue("FloodingRain", magnitudeIncreaseThreshold=4) - if self._threatTrendValue is not None: + if threatTrendValue is not None: # Convert the threat trend to a sentence threatTrendSentence = \ - self._getThreatTrendSentence("flooding rain", self._threatTrendValue) + self._getThreatTrendSentence("flooding rain", threatTrendValue) self._setProductPartValue(segmentDict, 'threatTrend', threatTrendSentence) def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): - tr = self._textProduct._windThreatStatementsTr[self._segment] + self._tr = self._textProduct._windThreatStatementsTr[self._segment] - self._setThreatStatementsProductParts(segmentDict, productSegment, tr) + self._setThreatStatementsProductParts(segmentDict, productSegment, self._tr) def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() @@ -1852,12 +2032,40 @@ class FloodingRainSection(SectionCommon): self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) if len(subsectionDict) > 0: self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + def _specialImpactsStatements(self): + return {"hunker down": ["Potential impacts from flooding rain are still unfolding.", + "The extent of realized impacts will depend on actual rainfall amounts as received at particular locations.", + ], + "recovery": ["For additional information on impacts being caused by flooding rain, refer to the local hazardous weather outlook or hurricane local statement.", + ], + "default": {"Extreme": ["If realized, extreme rainfall flooding may prompt numerous evacuations and rescues.", + "Rivers and tributaries may overwhelmingly overflow their banks in many places with deep moving water. Small streams, creeks, canals, arroyos, and ditches may become raging rivers. In mountain areas, deadly runoff may rage down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", + "Flood waters can enter numerous structures within multiple communities, some structures becoming uninhabitable or washed away. Numerous places where flood waters may cover escape routes. Streets and parking lots become rivers of raging water with underpasses submerged. Driving conditions become very dangerous. Numerous road and bridge closures with some weakened or washed out.", + ], + "High": ["If realized, major rainfall flooding may prompt many evacuations and rescues.", + "Rivers and tributaries may rapidly overflow their banks in multiple places. Small streams, creeks, canals, arroyos, and ditches may become dangerous rivers. In mountain areas, destructive runoff may run quickly down valleys while increasing susceptibility to rockslides and mudslides. Flood control systems and barriers may become stressed.", + "Flood waters can enter many structures within multiple communities, some structures becoming uninhabitable or washed away. Many places where flood waters may cover escape routes. Streets and parking lots become rivers of moving water with underpasses submerged. Driving conditions become dangerous. Many road and bridge closures with some weakened or washed out.", + ], + "Mod": ["If realized, moderate rainfall flooding may prompt several evacuations and rescues.", + "Rivers and tributaries may quickly become swollen with swifter currents and overspill their banks in a few places, especially in usually vulnerable spots. Small streams, creeks, canals, arroyos, and ditches overflow.", + "Flood waters can enter some structures or weaken foundations. Several places may experience expanded areas of rapid inundation at underpasses, low-lying spots, and poor drainage areas. Some streets and parking lots take on moving water as storm drains and retention ponds overflow. Driving conditions become hazardous. Some road and bridge closures.", + ], + "Elevated": ["If realized, localized rainfall flooding may prompt a few evacuations.", + "Rivers and tributaries may quickly rise with swifter currents. Small streams, creeks, canals, arroyos, and ditches may become swollen and overflow in spots.", + "Flood waters can enter a few structures, especially in usually vulnerable spots. A few places where rapid ponding of water occurs at underpasses, low-lying spots, and poor drainage areas. Several storm drains and retention ponds become near-full and begin to overflow. Some brief road and bridge closures.", + ], + "None": ["Little to no potential impacts from flooding rain.", + ] + } + } class TornadoSection(SectionCommon): def __init__(self, textProduct, segment, stats): SectionCommon.__init__(self, textProduct, segment, "Tornado") self._sectionName = 'tornadoSection[\'' + segment + '\']' self._stats = stats + self.isThreatInAllAdvisories = self._isThreatInAllAdvisories("TornadoThreat") def sectionParts(self, segment_vtecRecords_tuple): parts = [ @@ -1872,11 +2080,12 @@ class TornadoSection(SectionCommon): def _forecastSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() self._latestForecastSummary(subsectionDict, productSegmentGroup, productSegment) + self._tornadoSituation(subsectionDict, productSegmentGroup, productSegment) if len(subsectionDict) > 0: self._setProductPartValue(segmentDict, 'forecastSubsection', subsectionDict) def _latestForecastSummary(self, segmentDict, productSegmentGroup, productSegment): - summary = "There is no Tornado Watch in effect" + summary = "" segment, vtecRecords = productSegment headlines, _ = self._textProduct._getAdditionalHazards() @@ -1891,9 +2100,26 @@ class TornadoSection(SectionCommon): # Make sure it is for our zone if self._segment in areaList: summary = "Tornado Watch is in effect" - + self._setProductPartValue(segmentDict, 'latestForecastSummary', "Latest Local Forecast: " + summary) + + def _tornadoSituation(self, segmentDict, productSegmentGroup, productSegment): + + # Now add the bullet about tornado situation + if self._stats._maxThreat in ["Extreme", "High"]: + qualifier = "very favorable" + elif self._stats._maxThreat in ["Mod"]: + qualifier = "favorable" + elif self._stats._maxThreat in ["Elevated"]: + qualifier = "somewhat favorable" + else: + qualifier = "unfavorable" + + words = "Situation is %s for tornadoes" % (qualifier) + + self._setProductPartValue(segmentDict, 'tornadoSituation', words) + def _threatSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() @@ -1904,21 +2130,21 @@ class TornadoSection(SectionCommon): self._setProductPartValue(segmentDict, 'threatSubsection', subsectionDict) def _threatTrend(self, segmentDict, productSegmentGroup, productSegment): - self._threatTrendValue = self._getThreatTrendValue("Tornado", + threatTrendValue = self._getThreatTrendValue("Tornado", magnitudeIncreaseThreshold=None) - if self._threatTrendValue is not None: + if threatTrendValue is not None: # Convert the threat trend to a sentence threatTrendSentence = \ - self._getThreatTrendSentence("tornado", self._threatTrendValue) + self._getThreatTrendSentence("tornado", threatTrendValue) self._setProductPartValue(segmentDict, 'threatTrend', threatTrendSentence) def _threatStatements(self, segmentDict, productSegmentGroup, productSegment): - tr = self._textProduct._windThreatStatementsTr[self._segment] + self._tr = self._textProduct._windThreatStatementsTr[self._segment] - self._setThreatStatementsProductParts(segmentDict, productSegment, tr) + self._setThreatStatementsProductParts(segmentDict, productSegment, self._tr) def _impactsSubsection(self, segmentDict, productSegmentGroup, productSegment): subsectionDict = collections.OrderedDict() @@ -1926,6 +2152,33 @@ class TornadoSection(SectionCommon): self._potentialImpactsStatements(subsectionDict, productSegmentGroup, productSegment) if len(subsectionDict) > 0: self._setProductPartValue(segmentDict, 'impactsSubsection', subsectionDict) + + def _specialImpactsStatements(self): + return {"hunker down": ["Potential impacts from tropical tornadoes are still unfolding.", + "The extent of realized impacts will depend on the severity of actual tornado occurrence as experienced at particular locations.", + ], + "recovery": ["For additional information on impacts being caused by tropical tornadoes, refer to the local hazardous weather outlook or hurricane local statement.", + ], + "default": {"Extreme": ["The occurrence of an outbreak of tornadoes can greatly hinder the execution of other emergency activities during tropical events.", + "If realized, many places may experience tornado damage, with several spots of immense destruction, power loss, and communications failures.", + "Locations could realize sturdy buildings demolished, structures upon weak foundations swept away, mobile homes obliterated, large trees twisted and snapped with some debarked, vehicles lifted off the ground and thrown with distance, and small boats destroyed. Large and deadly projectiles can add considerably to the toll.", + ], + "High": ["The occurrence of numerous tornadoes can greatly hinder the execution of other emergency activities during tropical events.", + "If realized, many places may experience tornado damage with a few spots of immense destruction, power loss, and communications failures.", + "Locations could realize roof and wall failures of sturdy buildings with some being leveled, structures upon weak foundations blown away, mobile homes obliterated, large trees twisted and snapped with forested trees uprooted, vehicles lifted off the ground and thrown, and small boats destroyed. Large and deadly projectiles can add to the toll.", + ], + "Mod": ["The occurrence of scattered tornadoes can hinder the execution of other emergency activities during tropical events.", + "If realized, several places may experience tornado damage with a few spots of considerable damage, power loss, and communications failures.", + "Locations could realize roofs torn off frame houses, mobile homes demolished, boxcars overturned, large trees snapped or uprooted, vehicles tumbled, and small boats tossed about. Dangerous projectiles can add to the toll.", + ], + "Elevated": ["The occurrence of isolated tornadoes can hinder the execution of other emergency activities during tropical events.", + "If realized, a few places may experience tornado damage, along with power and communications disruptions.", + "Locations could realize roofs peeled off buildings, chimneys toppled, mobile homes pushed off foundations or overturned, large tree tops and branches snapped off, shallow-rooted trees knocked over, moving vehicles blown off roads, and small boats pulled from moorings.", + ], + "None": ["Little to no potential impacts from tropical tornadoes.", + ] + } + } ############################################################### @@ -1939,6 +2192,7 @@ class SectionCommonStats(): self._initializeSegmentAdvisories() self._maxThreat = None + def _initializeSegmentAdvisories(self): self._currentAdvisory = self._textProduct._currentAdvisory['ZoneData'][self._segment] @@ -1957,7 +2211,8 @@ class SectionCommonStats(): def _updateThreatStats(self, tr, statDict, threatGridName): self._textProduct.debug_print("statDict = '%s'" % (self._textProduct._pp.pformat(statDict)), 1) - threatLevel = self._textProduct.getStats(statDict, threatGridName) + + threatLevel = self._textProduct._getStatValue(statDict, threatGridName) if threatLevel is not None: threatLevels = self._textProduct._threatKeyOrder() self._textProduct.debug_print("updateThreatStats for %s" % (threatGridName), 1) @@ -1970,7 +2225,7 @@ class SectionCommonStats(): def _calculateHourOffset(self, targetTime): seconds = targetTime.unixTime() - self._textProduct._issueTime_secs - hour = int(round(seconds/60/60)) + hour = int(round(seconds/60.0/60.0)) if hour < 0: hour = 0 @@ -2025,6 +2280,8 @@ class WindSectionStats(SectionCommonStats): tr, _ = timeRangeList[index] statDict = statList[index] + self._textProduct.debug_print("\n\ntr = %s statDict = %s" % (tr, statDict), 1) + for periodIndex, periodTr in enumerate(self._textProduct._periodList): self._textProduct.debug_print("\n\nperiodIndex = %d periodList tr = %s" % (periodIndex, repr(periodTr)), 1) if (periodIndex == 0) and (tr.startTime().unixTime() < periodTr.startTime().unixTime()): @@ -2034,6 +2291,9 @@ class WindSectionStats(SectionCommonStats): elif periodTr.contains(tr.startTime()): currentPeriod = periodIndex break + + self._textProduct.debug_print("\n\ncurrentPeriod index = %s" % (currentPeriod), 1) + self._textProduct.debug_print("\n\ncurrentPeriod tr = %s" % (self._textProduct._periodList[currentPeriod]), 1) self._updateStatsForPwsXXint(tr, statDict, "pws34int", pws34intStats) self._updateStatsForPwsXXint(tr, statDict, "pws64int", pws64intStats) @@ -2078,7 +2338,66 @@ class WindSectionStats(SectionCommonStats): self._currentAdvisory["WindThreat"] = self._maxThreat self._currentAdvisory["WindForecast"] = self._maxWind + + #====================================================================== + # Let operator know if any required stats are missing + missingGridsList = [] + + self._textProduct.debug_print("+"*60, 1) + self._textProduct.debug_print("In WindSectionStats._setStats", 1) + self._textProduct.debug_print("pws34intStats.max = %s" % (pws34intStats.max), 1) + self._textProduct.debug_print("pws64intStats.max = %s" % (pws64intStats.max), 1) + self._textProduct.debug_print("pwsT34Stats.periodWithFirstCorrectGrid = %s" % (pwsT34Stats.periodWithFirstCorrectGrid), 1) +# self._textProduct.debug_print("pwsT34Stats.endTime = '%s'" % (pwsT34Stats.endTime), 1) + self._textProduct.debug_print("pwsT64Stats.periodWithFirstCorrectGrid = %s" % (pwsT64Stats.periodWithFirstCorrectGrid), 1) +# self._textProduct.debug_print("pwsT64Stats.endTime = '%s'" % (pwsT64Stats.endTime), 1) + self._textProduct.debug_print("self._maxWind = %s" % (self._maxWind), 1) + self._textProduct.debug_print("self._maxGust = %s" % (self._maxGust), 1) + self._textProduct.debug_print("self._maxThreat = %s" % (self._maxThreat), 1) + + # Interval wind speed probabilities + if pws34intStats.max is None: + missingGridsList.append("pws34int") + + if pws64intStats.max is None: + missingGridsList.append("pws64int") + + # Incremental wind speed probabilities + if pwsT34Stats.periodWithFirstCorrectGrid is None: + missingGridsList.append("PWSD34") + missingGridsList.append("PWSN34") + + if pwsT64Stats.periodWithFirstCorrectGrid is None: + missingGridsList.append("PWSD64") + missingGridsList.append("PWSN64") + + # Deterministic wind + if self._maxWind is None: + missingGridsList.append("Wind") + + # Deterministic wind gust + if self._maxGust is None: + missingGridsList.append("WindGust") + + # Threat grid + if self._maxThreat is None: + missingGridsList.append("WindThreat") + + # If there are any missing grids - let the user know + if len(missingGridsList) > 0: + msg = "\n\nSome grids are missing! Please check these grids " + \ + "before trying to run the formatter again:\n" + + for item in missingGridsList: + msg += "\n%s" % (item) + + msg += "\n\n" + + # Throw a statistics exception + # (no point in continuing until the grids are fixed) + raise self._textProduct.StatisticsException(msg) + def _updateStatsForPwsXXint(self, tr, statDict, gridName, pwsXXintStats): pwsXXint = self._textProduct._getStatValue(statDict, gridName, "Max") @@ -2160,6 +2479,8 @@ class WindSectionStats(SectionCommonStats): maxPws = pwsNXX + # These two statements will need to be reevaluated when this product is + # expanded to the Pacific basin (MHB - 02/03/2015) elif pwsDXX is not None and tr.startTime().hour in [21, 0, 3]: self._textProduct.debug_print("Wind Window Debug: pwsTXXStats DAY ignored", 1) @@ -2174,6 +2495,11 @@ class WindSectionStats(SectionCommonStats): period = period - 1 # We dropped the first grid so we are off-by-one self._textProduct.debug_print("shifting period back 1...new period = %s" % (period), 1) + + # Just set the first correct period to period zero, if it hasn't + # been set yet, so the missing grid check will not fail + if pwsTXXStats.periodWithFirstCorrectGrid is None: + pwsTXXStats.periodWithFirstCorrectGrid = 0 if "64" in dayGridName: index = threshold64index @@ -2190,13 +2516,22 @@ class WindSectionStats(SectionCommonStats): threshold = thresholds[period][index] if maxPws > threshold: - pwsTXXStats.endTime = tr.endTime() + trEndTime = tr.endTime() + periodEndTime = self._textProduct._periodList[period].endTime() + + # Don't go past the end of the period + if trEndTime <= periodEndTime: + pwsTXXStats.endTime = trEndTime + else: + pwsTXXStats.endTime = periodEndTime + self._textProduct.debug_print("Wind Window Debug: probability threshold = %s (period index %s)" % (threshold, period), 1) self._textProduct.debug_print("Wind Window Debug: pwsTXXStats dayGridName = %s" % (dayGridName), 1) self._textProduct.debug_print("Wind Window Debug: pwsTXXStats nightGridName = %s" % (nightGridName), 1) self._textProduct.debug_print("Wind Window Debug: pwsTXXStats original tr = %s" % (self._textProduct._pp.pformat(tr)), 1) self._textProduct.debug_print("Wind Window Debug: pwsTXXStats maxPws = %s" %(self._textProduct._pp.pformat(maxPws)), 1) self._textProduct.debug_print("Wind Window Debug: pwsTXXStats endTime = %s" % (self._textProduct._pp.pformat(pwsTXXStats.endTime)), 1) + self._textProduct.debug_print("Wind Window Debug: period tr = %s" % (self._textProduct._pp.pformat(self._textProduct._periodList[period])), 1) def _updateWindTimeInfo(self, tr, wind, timeInfo, speed): if wind >= speed: @@ -2360,6 +2695,8 @@ class WindSectionStats(SectionCommonStats): # from its end if (trStartHour < utcNight) and (utcNight - trStartHour) > 1: return True + elif trStartHour >= utcDay: + return True # Handle "normal" case where "day" starts before "night" in UTC elif trStartHour >= utcDay and trStartHour < utcNight and \ @@ -2385,15 +2722,23 @@ class StormSurgeSectionStats(SectionCommonStats): phishEndTime = None possibleStop = 0 + # If this is an inland area, just move on + if statList == "InlandArea": + return + self._textProduct.debug_print("*"*100, 1) self._textProduct.debug_print("phishStartTime = %s phishEndTime = %s possibleStop = %d" % (str(phishStartTime), str(phishEndTime), possibleStop), 1) + self._textProduct.debug_print("%s" % (self._textProduct._pp.pformat(statList)), 1) for period in range(len(statList)): tr, _ = timeRangeList[period] statDict = statList[period] + self._textProduct.debug_print("tr = %s" % (self._textProduct._pp.pformat(tr)), 1) + self._textProduct.debug_print("statDict = %s" % (self._textProduct._pp.pformat(statDict)), 1) phishPeak = self._textProduct._getStatValue(statDict, "InundationMax", "Max") + self._textProduct.debug_print("%s phishPeak = %s" % (repr(tr), phishPeak), 1) if phishPeak is not None: if self._inundationMax is None or phishPeak > self._inundationMax: self._inundationMax = phishPeak @@ -2462,6 +2807,51 @@ class StormSurgeSectionStats(SectionCommonStats): self._currentAdvisory["StormSurgeForecast"] = \ int(self._inundationMax * 10.0) / 10.0 + #====================================================================== + # Let operator know if any required stats are missing - only if we + # are populating the storm surge section + + if self._textProduct._PopulateSurge: + + missingGridsList = [] + + self._textProduct.debug_print("+"*60, 1) + self._textProduct.debug_print("In StormSurgeSectionStats._setStats", 1) + self._textProduct.debug_print("self._inundationMax = '%s'" % + (self._inundationMax), 1) + self._textProduct.debug_print("self._onsetSurgeHour = '%s'" % + (self._onsetSurgeHour), 1) + self._textProduct.debug_print("self._maxThreat = '%s'" % + (self._maxThreat), 1) + + # Max inundation + if self._inundationMax is None: + missingGridsList.append("InundationMax") + missingGridsList.append("InundationTiming") + + # Inundation timing - only applies if any inundation forecast + if self._inundationMax >= 1 and self._onsetSurgeHour is None: + missingGridsList.append("InundationTiming") + + # Threat grid + if self._maxThreat is None: + missingGridsList.append("StormSurgeThreat") + + # If there are any missing grids - let the user know + if len(missingGridsList) > 0: + msg = "\n\nSome grids are missing! Please check these grids " + \ + "before trying to run the formatter again:\n" + + for item in missingGridsList: + msg += "\n%s" % (item) + + msg += "\n\n" + + self._textProduct.debug_print("%s" % (self._textProduct._pp.pformat(dir (self))), 1) + # Throw a statistics exception + # (no point in continuing until the grids are fixed) + raise self._textProduct.StatisticsException(msg) + class FloodingRainSectionStats(SectionCommonStats): def __init__(self, textProduct, segment, statList, timeRangeList, @@ -2479,15 +2869,13 @@ class FloodingRainSectionStats(SectionCommonStats): tr, _ = timeRangeList[period] statDict = statList[period] - stats = self._textProduct.getStats(statDict, "QPF") - if stats is not None: - for (value, tr) in stats: - - if value is not None: - if self._sumAccum is None: - self._sumAccum = value - else: - self._sumAccum += value + value = self._textProduct._getStatValue(statDict, "QPF") + + if value is not None: + if self._sumAccum is None: + self._sumAccum = value + else: + self._sumAccum += value self._updateThreatStats(tr, statDict, "FloodingRainThreat") @@ -2502,10 +2890,14 @@ class FloodingRainSectionStats(SectionCommonStats): tr, _ = timeRangeList[period] prevStatDict = extraRainfallStatList[period] - prevStats = self._textProduct.getStats(prevStatDict, "QPF") + prevStats = self._textProduct._getStatValue(prevStatDict, "QPF") self._textProduct.debug_print("prevStats = %s" % (prevStats), 1) if prevStats is not None: - self._prevAccum += prevStats + + if self._prevAccum is not None: + self._prevAccum += prevStats + else: + self._prevAccum = prevStats else: self._prevAccum = 0.00 @@ -2517,6 +2909,38 @@ class FloodingRainSectionStats(SectionCommonStats): # Otherwise, do not consider this sgnificant rainfall self._currentAdvisory["PreviousRainfall"] = 0.00 + #====================================================================== + # Let operator know if any required stats are missing + + missingGridsList = [] + + self._textProduct.debug_print("+"*60, 1) + self._textProduct.debug_print("In FloodingRainSectionStats._setStats", 1) + self._textProduct.debug_print("self._sumAccum = '%s'" % (self._sumAccum), 1) + self._textProduct.debug_print("self._maxThreat = '%s'" % (self._maxThreat), 1) + + # Rainfall forecast + if self._sumAccum is None: + missingGridsList.append("QPF") + + # Threat grid + if self._maxThreat is None: + missingGridsList.append("FloodingRainThreat") + + # If there are any missing grids - let the user know + if len(missingGridsList) > 0: + msg = "\n\nSome grids are missing! Please check these grids " + \ + "before trying to run the formatter again:\n" + + for item in missingGridsList: + msg += "\n%s" % (item) + + msg += "\n\n" + + # Throw a statistics exception + # (no point in continuing until the grids are fixed) + raise self._textProduct.StatisticsException(msg) + class TornadoSectionStats(SectionCommonStats): def __init__(self, textProduct, segment, statList, timeRangeList): @@ -2533,6 +2957,30 @@ class TornadoSectionStats(SectionCommonStats): self._currentAdvisory["TornadoThreat"] = self._maxThreat + #====================================================================== + # Let operator know if any required stats are missing + + missingGridsList = [] + + self._textProduct.debug_print("+"*60, 1) + self._textProduct.debug_print("In TornadoSectionStats._setStats", 1) + self._textProduct.debug_print("self._maxThreat = '%s'" % (self._maxThreat), 1) + + # Threat grid + if self._maxThreat is None: + missingGridsList.append("TornadoThreat") + + # If there are any missing grids - let the user know + if len(missingGridsList) > 0: + msg = "\n\nSome grids are missing! Please check these grids " + \ + "before trying to run the formatter again:\n" + + for item in missingGridsList: + msg += "\n%s" % (item) + + # Throw a statistics exception + # (no point in continuing until the grids are fixed) + raise self._textProduct.StatisticsException(msg) from xml.etree.ElementTree import Element, SubElement, tostring, dump @@ -2572,7 +3020,6 @@ class XMLFormatter(): 'issuanceTimeDate', 'segments', - 'ugcCodes', 'ugcHeader', 'vtecRecords', 'areaList', @@ -2636,6 +3083,7 @@ class XMLFormatter(): 'sectionHeader', 'forecastSubsection', 'latestForecastSummary', + 'tornadoSituation', 'threatSubsection', 'lifePropertyThreatSummary', 'threatStatements', From 632a6905a10787dc778fa5b7e0772ec6c489b5dc Mon Sep 17 00:00:00 2001 From: David Gillingham Date: Wed, 18 Feb 2015 13:07:25 -0600 Subject: [PATCH 19/34] Issue #4144: Fix python version SendPracticeProductRequest and VTECDecoder.py. Change-Id: I1f85fc5d92034200014cfe464b51f4f2d8ae9080 Former-commit-id: 8c0a0357027145cb09895723af898ee2d9f2aa6a [formerly dfbfd38194c88c70f3e75de06bfc7114c6bd3542] Former-commit-id: 1e86f4cb13d54bff52338807dc894be29dea9ee3 --- .../cli/src/VTECDecoder/VTECDecoder.py | 5 +- .../PracticeProductOfftimeRequest.py | 47 ------------------- .../activetable/SendPracticeProductRequest.py | 14 ++++++ .../uf/common/activetable/__init__.py | 2 - 4 files changed, 16 insertions(+), 52 deletions(-) delete mode 100644 pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/PracticeProductOfftimeRequest.py diff --git a/edexOsgi/com.raytheon.uf.tools.gfesuite/cli/src/VTECDecoder/VTECDecoder.py b/edexOsgi/com.raytheon.uf.tools.gfesuite/cli/src/VTECDecoder/VTECDecoder.py index 4a63f6c8b5..bf9de127c1 100644 --- a/edexOsgi/com.raytheon.uf.tools.gfesuite/cli/src/VTECDecoder/VTECDecoder.py +++ b/edexOsgi/com.raytheon.uf.tools.gfesuite/cli/src/VTECDecoder/VTECDecoder.py @@ -7,8 +7,7 @@ import sys, os, getopt, string import logging import traceback -#import dynamicserialize.dstypes.com.raytheon.uf.common.activetable.SendPracticeProductRequest as SendPracticeProductRequest -import dynamicserialize.dstypes.com.raytheon.uf.common.activetable.PracticeProductOfftimeRequest as PracticeProductOfftimeRequest +from dynamicserialize.dstypes.com.raytheon.uf.common.activetable import SendPracticeProductRequest from ufpy import ThriftClient from ufpy import TimeUtil @@ -26,7 +25,7 @@ class VTECDecoder(object): """ thriftClient = ThriftClient.ThriftClient(self._host, self._port, '/services') - request = PracticeProductOfftimeRequest() + request = SendPracticeProductRequest() request.setProductText(self._getProduct()) request.setDrtString(self._offtimeStr) request.setNotifyGFE(self._notifyGFE) diff --git a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/PracticeProductOfftimeRequest.py b/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/PracticeProductOfftimeRequest.py deleted file mode 100644 index d5511a979b..0000000000 --- a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/PracticeProductOfftimeRequest.py +++ /dev/null @@ -1,47 +0,0 @@ -## -# This software was developed and / or modified by Raytheon Company, -# pursuant to Contract DG133W-05-CQ-1067 with the US Government. -# -# U.S. EXPORT CONTROLLED TECHNICAL DATA -# This software product contains export-restricted data whose -# export/transfer/disclosure is restricted by U.S. law. Dissemination -# to non-U.S. persons whether in the United States or abroad requires -# an export license or other authorization. -# -# Contractor Name: Raytheon Company -# Contractor Address: 6825 Pine Street, Suite 340 -# Mail Stop B8 -# Omaha, NE 68106 -# 402.291.0100 -# -# See the AWIPS II Master Rights File ("Master Rights File.pdf") for -# further licensing information. -## - -# File auto-generated against equivalent DynamicSerialize Java class - -class PracticeProductOfftimeRequest(object): - - def __init__(self): - self.drtString = None - self.notifyGFE = None - self.productText = None - - def getDrtString(self): - return self.drtString - - def setDrtString(self, drtString): - self.drtString = drtString - - def getNotifyGFE(self): - return self.notifyGFE - - def setNotifyGFE(self, notifyGFE): - self.notifyGFE = notifyGFE - - def getProductText(self): - return self.productText - - def setProductText(self, productText): - self.productText = productText - diff --git a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/SendPracticeProductRequest.py b/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/SendPracticeProductRequest.py index bf992930fa..82eefc2e46 100644 --- a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/SendPracticeProductRequest.py +++ b/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/SendPracticeProductRequest.py @@ -23,8 +23,22 @@ class SendPracticeProductRequest(object): def __init__(self): + self.drtString = None + self.notifyGFE = None self.productText = None + def getDrtString(self): + return self.drtString + + def setDrtString(self, drtString): + self.drtString = drtString + + def getNotifyGFE(self): + return self.notifyGFE + + def setNotifyGFE(self, notifyGFE): + self.notifyGFE = notifyGFE + def getProductText(self): return self.productText diff --git a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/__init__.py b/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/__init__.py index d6497323a0..6b32bd4d22 100644 --- a/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/__init__.py +++ b/pythonPackages/dynamicserialize/dstypes/com/raytheon/uf/common/activetable/__init__.py @@ -32,7 +32,6 @@ __all__ = [ 'GetVtecAttributeResponse', 'OperationalActiveTableRecord', 'PracticeActiveTableRecord', - 'PracticeProductOfftimeRequest', 'SendPracticeProductRequest', 'VTECChange', 'VTECTableChangeNotification', @@ -51,7 +50,6 @@ from GetVtecAttributeRequest import GetVtecAttributeRequest from GetVtecAttributeResponse import GetVtecAttributeResponse from OperationalActiveTableRecord import OperationalActiveTableRecord from PracticeActiveTableRecord import PracticeActiveTableRecord -from PracticeProductOfftimeRequest import PracticeProductOfftimeRequest from SendPracticeProductRequest import SendPracticeProductRequest from VTECChange import VTECChange from VTECTableChangeNotification import VTECTableChangeNotification From 13f179588fa1cbfb5f646ebf6ffbdcb8ba31842f Mon Sep 17 00:00:00 2001 From: "Rici.Yu" Date: Thu, 19 Feb 2015 10:48:19 -0500 Subject: [PATCH 20/34] ASM #17120 - retrieve practice products in non-operational mode Change-Id: I2b76edaface7d185269e6e5ea5f032eeb3d4dab0 Former-commit-id: cc44f78fcfb75854b4ddb78afd1f903250c85166 [formerly 16dccf94319fbb3a617cc5e2360d0b0aa1c9cc9c] Former-commit-id: e62413c10b5e5843331eb359c4d05103a72da632 --- .../gfe/userPython/textUtilities/regular/TextUtils.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py index e4016803ff..83f6a10126 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/regular/TextUtils.py @@ -675,10 +675,11 @@ class TextUtils: def getPreviousProduct(self, productID, searchString="", version=0): # gets a previous product from the AWIPS database + from com.raytheon.viz.gfe.core import DataManagerUIFactory from com.raytheon.viz.gfe.product import TextDBUtil - # DR 15703 - always retrieve operational products - opMode = True + # Redmine #17120 - return to pre-DR 15703 behavior. + opMode = DataManagerUIFactory.getCurrentInstance().getOpMode().name() == "OPERATIONAL" previousProduct = TextDBUtil.retrieveProduct(productID, version, opMode) previousProduct = string.strip(previousProduct) From 54802f6bd466574d75763da071838c7e6a3cf582 Mon Sep 17 00:00:00 2001 From: Evan Polster Date: Wed, 18 Feb 2015 17:17:06 +0000 Subject: [PATCH 21/34] VLab Issue #6350 - Ensemble Tool: Convert UI threads to Job threads where appropriate; fixes #6350 Change-Id: Ic320252d9ecb6bdd23cfa254c12ac6cc76f103ec Former-commit-id: 0975ab2b7b562f4d94c27f62c7cea4415106652f [formerly 94fd474ebb348bf40de413b1bf28f72ebcbb16be] Former-commit-id: 78d150af9c3abcd10d98c44a1aac8f06c75e7bdc --- .../EnsembleToolDisplayCustomizer.java | 146 +- .../ui/viewer/EnsembleToolViewer.java | 1214 ++++++++++------- 2 files changed, 781 insertions(+), 579 deletions(-) diff --git a/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/display/control/EnsembleToolDisplayCustomizer.java b/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/display/control/EnsembleToolDisplayCustomizer.java index 170338008a..34c2e7f6f5 100644 --- a/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/display/control/EnsembleToolDisplayCustomizer.java +++ b/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/display/control/EnsembleToolDisplayCustomizer.java @@ -1,5 +1,8 @@ package gov.noaa.gsd.viz.ensemble.display.control; +import gov.noaa.gsd.viz.ensemble.display.rsc.GeneratedEnsembleGridResource; +import gov.noaa.gsd.viz.ensemble.display.rsc.histogram.HistogramResource; +import gov.noaa.gsd.viz.ensemble.display.rsc.timeseries.GeneratedTimeSeriesResource; import gov.noaa.gsd.viz.ensemble.navigator.ui.layer.EnsembleToolManager; import java.util.ArrayList; @@ -108,6 +111,10 @@ public class EnsembleToolDisplayCustomizer implements private static long LAST_TIME = 0; /* + * TODO: This is a minimal working capability for batching large + * requests. We need to re-address this in the next release for a + * cleaner solution. + * * Maximum wait period for batching; this value was chosen as the * minimum necessary wait time for a two-ensemble load (e.g. 500MB and * 850MB Heights). By waiting this long, both full products sets will @@ -115,7 +122,7 @@ public class EnsembleToolDisplayCustomizer implements * machine). */ - private final int ALLOW_FOR_BUNCHED_REQUESTS_PERIOD = 1800; + private final int ALLOW_FOR_BUNCHED_REQUESTS_PERIOD = 2500; // thread which acts upon batched requests private Thread forceReset = null; @@ -145,24 +152,20 @@ public class EnsembleToolDisplayCustomizer implements * approach will be improved in an upcoming DR */ @Override - public synchronized void notifyRemove(ResourcePair rp) - throws VizException { + public void notifyRemove(ResourcePair rp) throws VizException { /** - * Pass,if the resource is not interested by ensemble tool + * Ignore the resource if not compatible with the ensemble tool * */ - if (!isCompatibleResource(rp)) { - return; + if (isCompatibleResource(rp) || isGeneratedResource(rp)) { + /** + * Remove it from the resource manager and update GUI. + */ + if (getEditor() != null) { + EnsembleResourceManager.getInstance() + .syncRegisteredResource(getEditor()); + } } - /** - * Remove it from the resource manager and update GUI Should notice - * GUI - */ - if (getEditor() != null) { - EnsembleResourceManager.getInstance().syncRegisteredResource( - getEditor()); - } - } /** @@ -175,18 +178,39 @@ public class EnsembleToolDisplayCustomizer implements public void notifyAdd(ResourcePair rp) throws VizException { /** - * Return if the resource is not interested by ensemble tool - * + * Ignore if this resource is not compatible with the ensemble tool */ if (!isCompatibleResource(rp)) { return; } - // add the resource pair to the staging list ... - rp.getResource().registerListener((IInitListener) this); - synchronized (batchedPairs) { - batchedPairs.add(rp); + /** + * Only buffer the resources which are ensemble members. + * + * Otherwise, individual resources will get processed immediately. + */ + String ensembleId = ""; + if (rp.getResource() instanceof GridResource) { + ensembleId = ((GridResource) (rp.getResource())) + .getAnyGridRecord().getEnsembleId(); + } else if (rp.getResource() instanceof TimeSeriesResource) { + ensembleId = ((TimeSeriesResource) (rp.getResource())) + .getAdapter().getEnsembleId(); } + + if (ensembleId != null && ensembleId != "") { + // add the resource pair to the batching list ... + rp.getResource().registerListener((IInitListener) this); + synchronized (batchedPairs) { + batchedPairs.add(rp); + + } + } else { + // register immediately to the resource manager + EnsembleResourceManager.getInstance().registerResource( + rp.getResource(), getEditor(), true); + } + } /** @@ -196,22 +220,24 @@ public class EnsembleToolDisplayCustomizer implements */ private void addBatchedResourcesToManager() { - Iterator pairsIter = batchedPairs.iterator(); - ResourcePair rp = null; - - while (pairsIter.hasNext()) { - - rp = pairsIter.next(); - if (pairsIter.hasNext()) { - EnsembleResourceManager.getInstance().registerResource( - rp.getResource(), getEditor(), false); - - } else { - EnsembleResourceManager.getInstance().registerResource( - rp.getResource(), getEditor(), true); - } - } synchronized (batchedPairs) { + Iterator pairsIter = null; + + pairsIter = batchedPairs.iterator(); + ResourcePair rp = null; + + while (pairsIter.hasNext()) { + + rp = pairsIter.next(); + if (pairsIter.hasNext()) { + EnsembleResourceManager.getInstance().registerResource( + rp.getResource(), getEditor(), false); + + } else { + EnsembleResourceManager.getInstance().registerResource( + rp.getResource(), getEditor(), true); + } + } batchedPairs.clear(); } } @@ -229,13 +255,29 @@ public class EnsembleToolDisplayCustomizer implements private boolean isCompatibleResource(ResourcePair rp) { AbstractVizResource resource = rp.getResource(); - if ((resource != null) + if ((resource != null && !isGeneratedResource(rp)) && ((resource instanceof GridResource) || (resource instanceof TimeSeriesResource))) { return true; } return false; } + /** + * Check if the resource is interested by the ensemble tool. + * + * @param rp + * @return + */ + private boolean isGeneratedResource(ResourcePair rp) { + if (rp.getResource() instanceof HistogramResource + || rp.getResource() instanceof GeneratedEnsembleGridResource + || rp.getResource() instanceof GeneratedTimeSeriesResource) { + return true; + } + + return false; + } + /* * (non-Javadoc) * @@ -246,27 +288,19 @@ public class EnsembleToolDisplayCustomizer implements @Override public synchronized void inited(AbstractVizResource rsc) { + int wait_period = 0; /* * ensemble resources can come in a bunch ... wait for some time to - * pass to batch them ... + * pass to batch them ... also, give a couplpe of extra seconds to + * resources derived from the TimeSeries class. */ - if (((LAST_TIME == 0) || ((System.currentTimeMillis() - LAST_TIME) > ALLOW_FOR_BUNCHED_REQUESTS_PERIOD))) { + if (rsc instanceof TimeSeriesResource) { + wait_period = ALLOW_FOR_BUNCHED_REQUESTS_PERIOD + 2000; + } else { + wait_period = ALLOW_FOR_BUNCHED_REQUESTS_PERIOD; + } - /* - * TODO: this thread may be superstitious yet is being left here - * until we can verify it is not needed. The very first time any - * resource is inited we sleep for small period to allow other - * similar threads (e.g. in other plug-ins) to have priority in - * acting on the resource. - */ - if (LAST_TIME == 0) { - // poor man's object.wait() for the very first time through - try { - Thread.sleep(50); - } catch (InterruptedException e) { - // ignore - } - } + if (((LAST_TIME == 0) || ((System.currentTimeMillis() - LAST_TIME) > wait_period))) { /** * Register the resource in to the resource manager @@ -280,12 +314,12 @@ public class EnsembleToolDisplayCustomizer implements } forceReset = null; forceReset = new Thread(new ForceRefreshIfNecessary( - ALLOW_FOR_BUNCHED_REQUESTS_PERIOD)); + wait_period)); forceReset.start(); } } else { forceReset = new Thread(new ForceRefreshIfNecessary( - ALLOW_FOR_BUNCHED_REQUESTS_PERIOD)); + wait_period)); forceReset.start(); } } else { diff --git a/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/navigator/ui/viewer/EnsembleToolViewer.java b/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/navigator/ui/viewer/EnsembleToolViewer.java index d21d44c84c..f48cbef910 100644 --- a/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/navigator/ui/viewer/EnsembleToolViewer.java +++ b/gsd/gov.noaa.gsd.viz.ensemble/src/gov/noaa/gsd/viz/ensemble/navigator/ui/viewer/EnsembleToolViewer.java @@ -27,6 +27,10 @@ import java.util.Set; import org.eclipse.core.commands.Command; import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Status; +import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.IMenuListener; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; @@ -271,6 +275,14 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { private Cursor waitCursor = null; + private final long elegantWaitPeriod = 100; + + /* class scope for accessibility from inner anonymous class */ + protected TreeItem foundItem = null; + + /* class scope for accessibility from inner anonymous class */ + protected TreeItem[] directDescendants = null; + private static boolean isDisposing = false; public static boolean isDisposing() { @@ -301,7 +313,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { */ public void createPartControl(Composite parent) { - // create the defaut icons when the view is initially opened + /* create the defaut icons when the view is initially opened */ constructImages(); owner = parent; @@ -314,16 +326,16 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { 3); parent.setLayoutData(gridData1); - // spacer component + /* spacer component */ Composite dummySpacerContainer = new Composite(parent, SWT.NONE); GridLayout gl_dummySpacerContainer = new GridLayout(); dummySpacerContainer.setLayout(gl_dummySpacerContainer); - // the main container holds the resource "legends" and is scrollable + /* the main container holds the resource "legends" and is scrollable */ tabContainer = new ScrolledComposite(parent, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL); - // this is the root grid layout of all grid layouts + /* this is the root grid layout of all grid layouts */ GridLayout gl_scrolledContainer = new GridLayout(); GridData gd_tabContainer = new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1); @@ -331,14 +343,16 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { tabContainer.setLayout(gl_scrolledContainer); tabContainer.setLayoutData(gd_tabContainer); - // main tab container to allow user to switch between legends, matrix, - // and future ideas ... + /* + * main tab container to allow user to switch between legends, matrix, + * and future ideas ... + */ tabEnsemblesMain = new CTabFolder(tabContainer, SWT.TOP | SWT.BORDER); tabEnsemblesMain.setFont(viewFont); tabEnsemblesMain .setSelectionBackground(SWTResourceManager.PALE_LIGHT_AZURE); - // here's the toolbar + /* here's the toolbar */ Composite toolbarContainer = new Composite(tabEnsemblesMain, SWT.NONE); toolbarContainer.setBackground(SWTResourceManager.WHITE); @@ -347,7 +361,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { fl_toolbarContainer.marginHeight = 1; toolbarContainer.setLayout(fl_toolbarContainer); - // fill the tool bar + /* fill the tool bar */ toolBar = makeToolBar(toolbarContainer); Rectangle r = toolbarContainer.getBounds(); r.height = r.height + 32; @@ -358,16 +372,19 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { tabEnsemblesMain.setFont(SWTResourceManager.getFont("SansSerif", 10, SWT.NONE)); - // tab entry for Legends + /* tab entry for Legends */ CTabItem tbtmLegends = new CTabItem(tabEnsemblesMain, SWT.NONE); tbtmLegends.setText(" Legends "); - // tab entry for Matrix + /* tab entry for Matrix */ CTabItem tbtmMatrix = new CTabItem(tabEnsemblesMain, SWT.NONE); tbtmMatrix.setText(" Matrix "); - // let's have an upper sash and lower sash that the user can - // resize vertically (see SWT concept of sash) + /* + * let's have an upper sash and lower sash that the user can resize + * vertically (see SWT concept of sash) + */ + sashForm = new SashForm(tabEnsemblesMain, SWT.BORDER); sashForm.setOrientation(SWT.VERTICAL); @@ -375,7 +392,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { tabContainer.setMinSize(tabEnsemblesMain.computeSize(SWT.DEFAULT, SWT.DEFAULT)); - // upper sash contains the resource "legend" tree. + /* upper sash contains the resource "legend" tree. */ ensembleTree = new Tree(sashForm, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.SINGLE | SWT.FULL_SELECTION); ensembleTree.setLinesVisible(true); @@ -383,14 +400,16 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { ensemblesTreeViewer = new TreeViewer(ensembleTree); createColumns(ensemblesTreeViewer); - // keep track of the collapse/expand state + /* keep track of the collapse/expand state */ ensemblesTreeViewer.addTreeListener(expandCollapseListener); - // recognize when the user clicks on something in the tree + /* recognize when the user clicks on something in the tree */ ensembleTree.addMouseListener(new EnsembleTreeMouseListener()); - // the lower sash contains a composite which itself contains - // a tab folder (i.e. set of tabs in one container) ... + /* + * the lower sash contains a composite which itself contains a tab + * folder (i.e. set of tabs in one container) ... + */ Composite lowerSash = new Composite(sashForm, SWT.BORDER_SOLID); lowerSash.setBackground(SWTResourceManager.LIGHT_GRAY); GridLayout gl_lowerSash = new GridLayout(); @@ -401,20 +420,21 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { gd_lowerSash.verticalIndent = 1; lowerSash.setLayoutData(gd_lowerSash); - // put the Info, ERF, and Preferences tabs in the lower sash - // container + /* + * put the Info, ERF, and Preferences tabs in the lower sash container + */ fillLowerSash(lowerSash); - // what is the best ratio of visibility of upper vs. lower sash - sashForm.setWeights(new int[] { 60, 40 }); + /* what is the best ratio of visibility of upper vs. lower sash */ + sashForm.setWeights(new int[] { 54, 46 }); tbtmLegends.setControl(sashForm); - // keep track of which lower tab was last selected + /* keep track of which lower tab was last selected */ tabFolder_lowerSash .addSelectionListener(new LowerSashTabSelectionListener()); - // fill the contents of the tree with + /* fill the contents of the tree with */ ensemblesTreeViewer .setContentProvider(new EnsembleTreeContentProvider()); ensemblesTreeViewer.setSorter(new EnsembleTreeSorter()); @@ -452,7 +472,6 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { waitCursor = owner.getDisplay().getSystemCursor(SWT.CURSOR_WAIT); ensembleTree.addKeyListener(new EnsembleTreeKeyListener()); - updateCursor(normalCursor); } @@ -502,15 +521,16 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { protected boolean anyChildrenToggleOn(String productName) { boolean anyChildrenToggledOn = false; - TreeItem parentItem = this.findTreeItemByLabelName(productName); + TreeItem parentItem = findTreeItemByLabelName(productName); List descendants = new ArrayList(); getAllDescendants(parentItem, descendants); - // TODO: this test is due to the fact that for some - // reason the ensemble children aren't being initially - // recognized as actual members of the ensemble root - // TreeItem even though they are really there. + /* + * TODO: this test is due to the fact that for some reason the ensemble + * children aren't being initially recognized as actual members of the + * ensemble root TreeItem even though they are really there. + */ if (descendants.size() == 0) { return true; } @@ -544,70 +564,93 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { * children resources of a given parent are invisible then the parent tree * item should be grayed out. */ - protected void matchParentToChildrenVisibility(TreeItem childItem) { + protected void matchParentToChildrenVisibility(final TreeItem childItem) { - final TreeItem parentItem = childItem.getParentItem(); - if (parentItem == null) - return; + VizApp.runSync(new Runnable() { - final Object d = parentItem.getData(); - if (d instanceof String) { + @Override + public void run() { - List descendants = new ArrayList(); - getAllDescendants(parentItem, descendants); + final TreeItem parentItem = childItem.getParentItem(); + if (parentItem == null) + return; - boolean ai = true; + final Object d = parentItem.getData(); + if (d instanceof String) { - for (TreeItem ti : descendants) { - Object data = ti.getData(); - if (data instanceof GenericResourceHolder) { - GenericResourceHolder gr = (GenericResourceHolder) data; - AbstractVizResource rsc = gr.getRsc(); - if (rsc.getProperties().isVisible()) { - ai = false; - break; + List descendants = new ArrayList(); + getAllDescendants(parentItem, descendants); + + boolean ai = true; + + for (TreeItem ti : descendants) { + Object data = ti.getData(); + if (data instanceof GenericResourceHolder) { + GenericResourceHolder gr = (GenericResourceHolder) data; + AbstractVizResource rsc = gr.getRsc(); + if (rsc.getProperties().isVisible()) { + ai = false; + break; + } + } + } + + final boolean allInvisible = ai; + + /* if all invisible then make sure the parent is grayed-out. */ + if (allInvisible) { + parentItem + .setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); + if (isViewerTreeReady()) { + ensemblesTreeViewer.getTree().deselectAll(); + } + } + /* + * otherwise, if any one item is visible then make sure the + * parent is normalized. + */ + + else { + parentItem + .setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); + if (isViewerTreeReady()) { + ensemblesTreeViewer.getTree().deselectAll(); + } } } + } - final boolean allInvisible = ai; - - // if all invisible then make sure the parent is grayed-out. - if (allInvisible) { - parentItem - .setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - // otherwise, if any one item is visible then make sure the parent - // is normalized. - else { - parentItem - .setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - - } + }); } /* * This searches only root level items to see if a root item of a given name * has any children (ensemble members) and, if so, returns those children. */ - private TreeItem findTreeItemByLabelName(String name) { + private TreeItem findTreeItemByLabelName(final String name) { - TreeItem[] allRoots = ensembleTree.getItems(); - TreeItem foundItem = null; + VizApp.runSync(new Runnable() { - for (TreeItem ti : allRoots) { - if (String.class.isAssignableFrom(ti.getData().getClass())) { - String treeItemLabelName = (String) ti.getData(); - if (treeItemLabelName.compareTo(name) == 0) { - foundItem = ti; - break; + @Override + public void run() { + TreeItem[] allRoots = ensembleTree.getItems(); + + for (TreeItem ti : allRoots) { + if (ti.getData() instanceof String) { + String treeItemLabelName = (String) ti.getData(); + if (treeItemLabelName.equals(name)) { + foundItem = ti; + break; + } + } } + } - } + + }); return foundItem; + } /* @@ -678,8 +721,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { clp.dispose(); } } - if ((ensembleTree != null) - && (!ensemblesTreeViewer.getTree().isDisposed())) { + if (isViewerTreeReady()) { ensembleTree.removeAll(); ensembleTree.dispose(); ensembleTree = null; @@ -705,7 +747,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { @Override public void setFocus() { - if (ensemblesTreeViewer != null) { + if (isViewerTreeReady()) { ensemblesTreeViewer.getControl().setFocus(); } } @@ -715,7 +757,10 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { * Browser was opened. */ public void grabFocus() { - ensemblesTreeViewer.getControl().forceFocus(); + + if (isViewerTreeReady()) { + ensemblesTreeViewer.getControl().forceFocus(); + } } synchronized public boolean isEnabled() { @@ -740,99 +785,85 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { */ private void toggleItemVisible(final TreeItem item) { - final Object mousedItem = item.getData(); - if (mousedItem instanceof String) { + final IRefreshListener viewer = this; + VizApp.runSync(new Runnable() { - Color fg = item.getForeground(); - boolean iv = false; + @Override + public void run() { - // Awkward way of seeing if the item has been already - // grayed-out. This needs to be further evaluated for a - // better solution. - if (fg.getRGB().equals(DISABLED_FOREGROUND_COLOR.getRGB())) { - iv = false; - } else { - iv = true; + if (item.isDisposed()) { + return; + } + final Object mousedItem = item.getData(); + if (mousedItem instanceof String) { + + Color fg = item.getForeground(); + boolean isVisible = false; + + /* + * Awkward way of seeing if the item has been already + * grayed-out. This needs to be further evaluated for a + * better solution. + */ + if (fg.getRGB().equals(DISABLED_FOREGROUND_COLOR.getRGB())) { + isVisible = false; + } else { + isVisible = true; + } + + /* if it was on turn it off */ + if (isVisible) { + item.setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); + if (isViewerTreeReady()) { + ensemblesTreeViewer.getTree().deselectAll(); + } + } + /* if it was off turn it on */ + else { + item.setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); + if (isViewerTreeReady()) { + ensemblesTreeViewer.getTree().deselectAll(); + } + } + + } else if (mousedItem instanceof GenericResourceHolder) { + + GenericResourceHolder gr = (GenericResourceHolder) mousedItem; + gr.getRsc().registerListener(viewer); + boolean isVisible = gr.getRsc().getProperties().isVisible(); + /* toggle visibility */ + isVisible = !isVisible; + gr.getRsc().getProperties().setVisible(isVisible); + + /* update tree item to reflect new state */ + if (isVisible) { + item.setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); + } else { + item.setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); + } + if (isViewerTreeReady()) { + ensemblesTreeViewer.getTree().deselectAll(); + } + } } - final boolean isVisible = iv; - - // if it was on turn it off - if (isVisible) { - item.setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - // if it was off turn it on - else { - item.setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - ensemblesTreeViewer.refresh(true); - - } else if (mousedItem instanceof GenericResourceHolder) { - - GenericResourceHolder gr = (GenericResourceHolder) mousedItem; - // toggle visibility - gr.getRsc().getProperties() - .setVisible(!gr.getRsc().getProperties().isVisible()); - gr.getRsc().issueRefresh(); - - // update tree item to reflect new state - if (gr.getRsc().getProperties().isVisible()) { - item.setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } else { - item.setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - ensemblesTreeViewer.refresh(true); - } + }); + refresh(); } - /* - * Given a tree item and a visibility state, find the item in the tree and - * set it's visibility to the given state. - */ - private void setItemVisible(TreeItem item, final boolean isVisible) { + protected TreeItem[] getDirectDescendants(final TreeItem rootItem) { - if ((!item.isDisposed()) && (item != null)) { - final Object mousedItem = item.getData(); - final TreeItem givenItem = item; - if (mousedItem instanceof String) { + VizApp.runSync(new Runnable() { - if (isVisible) { - givenItem - .setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } else { - givenItem - .setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - ensemblesTreeViewer.refresh(); - - } else if (mousedItem instanceof GenericResourceHolder) { - - GenericResourceHolder gr = (GenericResourceHolder) mousedItem; - - // toggle visibility - gr.getRsc().getProperties().setVisible(isVisible); - gr.getRsc().issueRefresh(); - - // update tree item to reflect new state - if (gr.getRsc().getProperties().isVisible()) { - givenItem - .setForeground(EnsembleToolViewer.ENABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } else { - givenItem - .setForeground(EnsembleToolViewer.DISABLED_FOREGROUND_COLOR); - ensemblesTreeViewer.getTree().deselectAll(); - } - ensemblesTreeViewer.refresh(); + @Override + public void run() { + directDescendants = rootItem.getItems(); } - } + + }); + + return directDescendants; } /* @@ -1549,45 +1580,60 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { @Override public void mouseDown(MouseEvent event) { - // The mouseDown event is what initiates the - // displaying of a context-sensitive popup - // menu for either ensemble products or - // individual grid products ... + /** + * The mouseDown event is what initiates the displaying of a + * context-sensitive popup menu for either ensemble products or + * individual grid products ... + */ - // if the ensemble tool isn't open then ignore - // user mouse clicks ... - if (!EnsembleToolManager.getInstance().isReady()) { + /* + * if the ensemble tool isn't open then ignore user mouse clicks ... + */ + if (!EnsembleToolManager.getInstance().isReady() + || !isViewerTreeReady()) { return; } // get the tree item that was clicked on ... Point point = new Point(event.x, event.y); - final TreeItem item = ensembleTree.getItem(point); - // is this a mouse-button-3 (e.g. typical RIGHT-CLICK) - // over a tree item? - if ((item != null) && (event.button == 3)) { - // let's put up a context-sensitive menu similar to cave legend - // pop-up menu ... - final Object mousedItem = item.getData(); + final TreeItem userClickedTreeItem = ensembleTree.getItem(point); + if (userClickedTreeItem == null || userClickedTreeItem.isDisposed()) { + return; + } - // Currently, top level tree items (which also contain child - // tree items) are always Ensemble names (unique strings). So - // if the user clicks on this item then display the popup menu - // for the Ensemble product. + /* + * is this a mouse-button-3 (e.g. typical RIGHT-CLICK) over a tree + * item? + */ + if ((userClickedTreeItem != null) && (event.button == 3)) { + /* + * let's put up a context-sensitive menu similar to cave legend + * pop-up menu ... + */ + final Object mousedItem = userClickedTreeItem.getData(); + + /* + * Currently, top level tree items (which also contain child + * tree items) are always Ensemble names (unique strings). So if + * the user clicks on this item then display the popup menu for + * the Ensemble product. + */ if (mousedItem instanceof String) { final Menu legendMenu = new Menu(owner.getShell(), SWT.POP_UP); final String mousedEnsembleName = (String) mousedItem; - // relative frequency menu item allows the user to generate - // a probability display demonstrating the chance a value - // p(x) lies within a range, outside a range, above a - // threshold, or below a threshold. + /* + * Relative frequency menu item allows the user to generate + * a probability display demonstrating the chance a value + * p(x) lies within a range, outside a range, above a + * threshold, or below a threshold. + */ addERFLayerMenuItem = new MenuItem(legendMenu, SWT.PUSH); addERFLayerMenuItem.setText("Relative Frequency"); - // only enable the RF menu item if we are in plan view + /* only enable the RF menu item if we are in plan view */ if (editorType == ResourceType.TIME_SERIES) { addERFLayerMenuItem.setEnabled(false); } @@ -1600,27 +1646,22 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { public void handleEvent(Event event) { - VizApp.runAsync(new Runnable() { - - @Override - public void run() { - - updateCursor(waitCursor); - startAddERFLayer(mousedEnsembleName); - updateCursor(normalCursor); - - } - }); + updateCursor(waitCursor); + startAddERFLayer(mousedEnsembleName); + updateCursor(normalCursor); } }); - // TODO Need to have a easy access Mean calculation on - // an Ensemble product ... + /* + * TODO Need to have a easy access Mean calculation on an + * Ensemble product ... + */ - // this menu item allows the user to choose a color - // gradient for either the SREF or GEFS ensemble - // products. + /* + * This menu item allows the user to choose a color gradient + * for either the SREF or GEFS ensemble products. + */ MenuItem ensembleColorizeMenuItem = new MenuItem( legendMenu, SWT.PUSH); ensembleColorizeMenuItem.setText("Color Gradient"); @@ -1629,22 +1670,15 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { new Listener() { public void handleEvent(Event event) { - VizApp.runAsync(new Runnable() { + updateColorsOnEnsembleResource(mousedEnsembleName); - @Override - public void run() { - - updateCursor(waitCursor); - updateColorsOnResource(mousedEnsembleName); - updateCursor(normalCursor); - - } - }); } }); - // this menu item allows the user to remove the ensemble and - // all of its members. + /* + * this menu item allows the user to remove the ensemble and + * all of its members. + */ MenuItem unloadRscMenuItem = new MenuItem(legendMenu, SWT.PUSH); unloadRscMenuItem.setText("Unload Members"); @@ -1660,25 +1694,19 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { if (proceed) { - VizApp.runAsync(new Runnable() { - - @Override - public void run() { - updateCursor(waitCursor); - EnsembleToolManager - .getInstance() - .unloadResourcesByName( - item.getText()); - updateCursor(normalCursor); - } - }); - + EnsembleToolManager.getInstance() + .unloadResourcesByName( + userClickedTreeItem + .getText()); } + } }); - // this menu item allows the user to hide/show an ensemble - // product and all of its members. + /* + * this menu item allows the user to hide/show an ensemble + * product and all of its members. + */ final MenuItem toggleVisibilityMenuItem = new MenuItem( legendMenu, SWT.CHECK); toggleVisibilityMenuItem.setText("Display Product"); @@ -1688,29 +1716,28 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { public void handleEvent(Event event) { - VizApp.runAsync(new Runnable() { + ToggleProductVisiblityJob ccj = new ToggleProductVisiblityJob( + "Toggle Product Visibility"); + ccj.setPriority(Job.INTERACTIVE); + ccj.setTargetTreeItem(userClickedTreeItem); + /* + * interactive, yes, but don't race other + * Jobs + */ + ccj.schedule(elegantWaitPeriod); - @Override - public void run() { - - updateCursor(waitCursor); - setItemVisible(item, - toggleVisibilityMenuItem - .getSelection()); - updateCursor(normalCursor); - - } - }); } }); legendMenu.setVisible(true); - // show the last selected non-transient tab ... the - // only transient tab is the ERF tab. The other two - // tabs are Info and Preferences. If one of the latter - // two tabs were previously chosen, then reopen that - // previously selected tab. + /* + * Show the last selected non-transient tab ... the only + * transient tab is the ERF tab. The other two tabs are Info + * and Preferences. If one of the latter two tabs were + * previously chosen, then reopen that previously selected + * tab. + */ if (lastSelectedNonTransientTabItem != null) { tabFolder_lowerSash .setSelection(lastSelectedNonTransientTabItem); @@ -1720,15 +1747,17 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { } else if (mousedItem instanceof GenericResourceHolder) { - // if the tree item the user clicked on was an individual - // grid product then show the context-sensitive popup menu - // for it ... + /* + * If the tree item the user clicked on was an individual + * grid product then show the context-sensitive popup menu + * for it ... + */ final GenericResourceHolder gr = (GenericResourceHolder) mousedItem; MenuManager menuMgr = new MenuManager("#PopupMenu"); menuMgr.setRemoveAllWhenShown(true); - // the popup menu is generated by the ContextMenuManager + /* The popup menu is generated by the ContextMenuManager */ menuMgr.addMenuListener(new IMenuListener() { public void menuAboutToShow(IMenuManager manager) { ResourcePair rp = EnsembleToolManager.getInstance() @@ -1746,15 +1775,19 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { final Menu legendMenu = menuMgr.createContextMenu(owner); legendMenu.setVisible(true); - ensemblesTreeViewer.refresh(); + if (isViewerTreeReady()) { + ensemblesTreeViewer.refresh(); + } - // disable the ui components on the ERF tab ... + /* disable the ui components on the ERF tab ... */ disableERFTabWidgets(); - // show the last selected non-transient tab ... the - // only transient tab is the ERF tab. The other two - // tabs are Info and Preferences. If one of the latter - // two tabs were previously chosen, then reopen that - // previously selected tab. + /* + * show the last selected non-transient tab ... the only + * transient tab is the ERF tab. The other two tabs are Info + * and Preferences. If one of the latter two tabs were + * previously chosen, then reopen that previously selected + * tab. + */ if (lastSelectedNonTransientTabItem != null) { tabFolder_lowerSash .setSelection(lastSelectedNonTransientTabItem); @@ -1763,7 +1796,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { } } - ensembleTree.deselect(item); + ensembleTree.deselect(userClickedTreeItem); } } @@ -1771,15 +1804,26 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { public void mouseUp(MouseEvent event) { Point point = new Point(event.x, event.y); + + if (!EnsembleToolManager.getInstance().isReady() + || !isViewerTreeReady()) { + return; + } + final TreeItem userClickedTreeItem = ensembleTree.getItem(point); - // the mouse up event currently only acts on items in the tree - // so if the tree item is null then just return ... - if (userClickedTreeItem == null) + /* + * The mouse up event currently only acts on items in the tree so if + * the tree item is null then just return ... + */ + if (userClickedTreeItem == null || userClickedTreeItem.isDisposed()) { return; + } - // keep track of the last highlighted resource and make sure - // it is still displayed properly ... + /* + * keep track of the last highlighted resource and make sure it is + * still displayed properly ... + */ if (EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE != null) { EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE.getCapability( OutlineCapability.class).setOutlineWidth( @@ -1793,30 +1837,30 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_OUTLINE_ASSERTED); } - // is this a CTRL-MB1 (e.g. CONTROL LEFT-CLICK) over a tree item? - // ... then this is a UI SWT item selection - if ((userClickedTreeItem != null) && (event.button == 1) - && ((event.stateMask & SWT.CTRL) != 0)) { + /* + * Is this a CTRL-MB1 (e.g. CONTROL LEFT-CLICK) over a tree item? + * ... then this is a UI SWT item selection + */ + if ((event.button == 1) && ((event.stateMask & SWT.CTRL) != 0)) { - final Object mousedItem = userClickedTreeItem.getData(); + final Object mousedObject = userClickedTreeItem.getData(); - // Ctrl-click on a item that is already selected deselects it. + /* Ctrl-click on a item that is already selected deselects it. */ if (EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE != null) { - if (mousedItem instanceof GenericResourceHolder) { - GenericResourceHolder grh = (GenericResourceHolder) mousedItem; + if (mousedObject instanceof GenericResourceHolder) { + GenericResourceHolder grh = (GenericResourceHolder) mousedObject; if (grh.getRsc() == EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE) { ensembleTree.deselect(userClickedTreeItem); EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = null; return; } } - } else if ((ensemblesTreeViewer != null) - && (ensemblesTreeViewer.getTree() != null)) { + } else if (isViewerTreeReady()) { - // Ctrl-click on a item that is not selected selects it. - if (mousedItem instanceof GenericResourceHolder) { - GenericResourceHolder grh = (GenericResourceHolder) mousedItem; + /* Ctrl-click on a item that is not selected selects it. */ + if (mousedObject instanceof GenericResourceHolder) { + GenericResourceHolder grh = (GenericResourceHolder) mousedObject; EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = grh .getRsc(); ensemblesTreeViewer.getTree().deselectAll(); @@ -1826,9 +1870,11 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { ensemblesTreeViewer.getTree().update(); } - // always display either the last opened tab or the information - // tab (i.e. only display the ERF tab when we the user is - // actively entering an ERF probablility). + /* + * Always display either the last opened tab or the information + * tab (i.e. only display the ERF tab when the user is actively + * entering an ERF probablility). + */ disableERFTabWidgets(); if (lastSelectedNonTransientTabItem != null) { tabFolder_lowerSash @@ -1837,155 +1883,150 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { tabFolder_lowerSash.setSelection(tabResourceInfo); } - // if the user selects an generated ERF product then update - // the ERF tabs ... - if (mousedItem instanceof GeneratedGridResourceHolder) { - GeneratedGridResourceHolder grh = (GeneratedGridResourceHolder) mousedItem; + /* + * if the user selects an generated ERF product then update the + * ERF tabs ... + */ + if (mousedObject instanceof GeneratedGridResourceHolder) { + GeneratedGridResourceHolder grh = (GeneratedGridResourceHolder) mousedObject; if (grh.getCalculation() == Calculation.ENSEMBLE_RELATIVE_FREQUENCY) { tabFolder_lowerSash.setSelection(tabERFLayerControl); setERFFields(grh.getRange(), grh.getUniqueName()); } } - // is this an individual grid product? - if (mousedItem instanceof GridResourceHolder) { - GridResourceHolder grh = (GridResourceHolder) mousedItem; + /* is this an individual grid product? */ + if (mousedObject instanceof GridResourceHolder) { + GridResourceHolder grh = (GridResourceHolder) mousedObject; - // only highlight a visible resource + /* only highlight a visible resource */ if ((grh.getRsc() != null) && (grh.getRsc().getProperties().isVisible())) { currentEnsembleRsc = grh.getRsc(); - // then highlight the resource in the primary pane - // and keep track of the resource as the most recently - // highlighted resource. - VizApp.runSync(new Runnable() { - - public void run() { - if ((currentEnsembleRsc != null) - && (thickenOnSelection)) { - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = currentEnsembleRsc; - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_WIDTH = currentEnsembleRsc - .getCapability( - OutlineCapability.class) - .getOutlineWidth(); - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_RGB = currentEnsembleRsc - .getCapability( - ColorableCapability.class) - .getColor(); - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_OUTLINE_ASSERTED = currentEnsembleRsc - .getCapability( - OutlineCapability.class) - .isOutlineOn(); - currentEnsembleRsc.getCapability( - OutlineCapability.class) - .setOutlineOn(true); - currentEnsembleRsc.getCapability( - OutlineCapability.class) - .setOutlineWidth(thickenWidth); - if (!useResourceColorOnThicken) { - currentEnsembleRsc.getCapability( - ColorableCapability.class) - .setColor( - thickenOnSelectionColor - .getRGB()); - } - currentEnsembleRsc.issueRefresh(); - } + /* + * Then highlight the resource in the primary pane and + * keep track of the resource as the most recently + * highlighted resource. + */ + if ((currentEnsembleRsc != null) + && (thickenOnSelection)) { + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = currentEnsembleRsc; + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_WIDTH = currentEnsembleRsc + .getCapability(OutlineCapability.class) + .getOutlineWidth(); + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_RGB = currentEnsembleRsc + .getCapability(ColorableCapability.class) + .getColor(); + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_OUTLINE_ASSERTED = currentEnsembleRsc + .getCapability(OutlineCapability.class) + .isOutlineOn(); + currentEnsembleRsc.getCapability( + OutlineCapability.class).setOutlineOn(true); + currentEnsembleRsc.getCapability( + OutlineCapability.class).setOutlineWidth( + thickenWidth); + if (!useResourceColorOnThicken) { + currentEnsembleRsc.getCapability( + ColorableCapability.class).setColor( + thickenOnSelectionColor.getRGB()); } - }); + } } } - // is this a generated product (e.g. a user-requested - // calculation) - if (mousedItem instanceof GeneratedGridResourceHolder) { - GeneratedGridResourceHolder grh = (GeneratedGridResourceHolder) mousedItem; + /* + * Is this a generated product (e.g. a user-requested + * calculation)? + */ + if (mousedObject instanceof GeneratedGridResourceHolder) { + GeneratedGridResourceHolder grh = (GeneratedGridResourceHolder) mousedObject; - // only highlight a visible resource + /* only highlight a visible resource */ if ((grh.getRsc() != null) && (grh.getRsc().getProperties().isVisible())) { currentEnsembleRsc = grh.getRsc(); - // then highlight the resource in the primary pane - // and keep track of the resource as the most recently - // highlighted resource. - VizApp.runSync(new Runnable() { - - public void run() { - if ((currentEnsembleRsc != null) - && (thickenOnSelection)) { - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = currentEnsembleRsc; - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_WIDTH = currentEnsembleRsc - .getCapability( - OutlineCapability.class) - .getOutlineWidth(); - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_RGB = currentEnsembleRsc - .getCapability( - ColorableCapability.class) - .getColor(); - EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_OUTLINE_ASSERTED = currentEnsembleRsc - .getCapability( - OutlineCapability.class) - .isOutlineOn(); - currentEnsembleRsc.getCapability( - OutlineCapability.class) - .setOutlineOn(true); - currentEnsembleRsc.getCapability( - OutlineCapability.class) - .setOutlineWidth(thickenWidth); - if (!useResourceColorOnThicken) { - currentEnsembleRsc.getCapability( - ColorableCapability.class) - .setColor( - thickenOnSelectionColor - .getRGB()); - } - currentEnsembleRsc.issueRefresh(); - } + /* + * Then highlight the resource in the primary pane and + * keep track of the resource as the most recently + * highlighted resource. + */ + if ((currentEnsembleRsc != null) + && (thickenOnSelection)) { + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE = currentEnsembleRsc; + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_WIDTH = currentEnsembleRsc + .getCapability(OutlineCapability.class) + .getOutlineWidth(); + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_RGB = currentEnsembleRsc + .getCapability(ColorableCapability.class) + .getColor(); + EnsembleToolViewer.LAST_HIGHLIGHTED_RESOURCE_OUTLINE_ASSERTED = currentEnsembleRsc + .getCapability(OutlineCapability.class) + .isOutlineOn(); + currentEnsembleRsc.getCapability( + OutlineCapability.class).setOutlineOn(true); + currentEnsembleRsc.getCapability( + OutlineCapability.class).setOutlineWidth( + thickenWidth); + if (!useResourceColorOnThicken) { + currentEnsembleRsc.getCapability( + ColorableCapability.class).setColor( + thickenOnSelectionColor.getRGB()); } - }); + } } } } - // is this a simple left-click (MB1) over a tree item? - else if ((userClickedTreeItem != null) && (event.button == 1)) { + /* Is this a simple left-click (MB1) over a tree item? */ + else if (event.button == 1) { + + /* + * By default, left-clicking on a tree item in the tree will + * toggle that product's visibility. + */ - // by default, left-clicking on a tree item in the - // tree will toggle that product's visibility. final Object mousedItem = userClickedTreeItem.getData(); - // A string means it is an ensemble product name + /* A string means it is an ensemble product name */ if (mousedItem instanceof String) { - // TODO: need a performant solution for toggling visibility - // of all the resources in one ensemble + + String ensembleName = (String) mousedItem; + + ToggleEnsembleVisiblityJob ccj = new ToggleEnsembleVisiblityJob( + "Toggle Ensemble Members Visibility"); + ccj.setPriority(Job.INTERACTIVE); + ccj.setTargetEnsembleProduct(ensembleName); + /* interactive, yes, but don't race other Jobs */ + ccj.schedule(elegantWaitPeriod); + } else if (mousedItem instanceof GenericResourceHolder) { - VizApp.runAsync(new Runnable() { - - @Override - public void run() { - - updateCursor(waitCursor); - toggleItemVisible(userClickedTreeItem); - - // if this was the last item to be toggled off, - // for example, in an ensemble group, then you - // need to toggle the parent tree item off also - matchParentToChildrenVisibility(userClickedTreeItem); - updateCursor(normalCursor); - } - - }); - + ToggleProductVisiblityJob ccj = new ToggleProductVisiblityJob( + "Toggle Product Visibility"); + ccj.setPriority(Job.INTERACTIVE); + ccj.setTargetTreeItem(userClickedTreeItem); + /* interactive, yes, but don't race other Jobs */ + ccj.schedule(elegantWaitPeriod); } } } } + private List perturbationMembers = null; + + public List getPerturbationMembers() { + return perturbationMembers; + } + + public void setPerturbationMembers( + List perturbationMembers) { + this.perturbationMembers = perturbationMembers; + } + /* * This method will update color on a given ensemble resource. Underlying * methods know how to colorize the contained perturbation members (eg. for @@ -1993,101 +2034,68 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { * * We need a better way of determining what type of resource we have. */ - protected void updateColorsOnResource(String ensembleName) { - - final List children = getEnsembleMemberGenericResources(ensembleName); - - // TODO: poor-man's way of knowing what type of flavor this ensemble is - if ((ensembleName.indexOf("GEFS") >= 0) - || (ensembleName.indexOf("GFS Ensemble") >= 0)) { - updateGEFSEnsembleColors(ensembleName, children); - } else if (ensembleName.indexOf("SREF") >= 0) { - updateSREFColors(ensembleName, children); - } - - } - - private void updateSREFColors(String ensembleName, - final List children) { + protected void updateColorsOnEnsembleResource(final String ensembleName) { VizApp.runSync(new Runnable() { @Override public void run() { - - updateCursor(waitCursor); - - EnsembleSREFColorChooser cd = new EnsembleSREFColorChooser( - owner.getShell()); - cd.setBlockOnOpen(true); - if (cd.open() == Window.OK) { - Color currColor = null; - for (GenericResourceHolder gRsc : children) { - if (gRsc instanceof GridResourceHolder) { - AbstractVizResource rsc = gRsc.getRsc(); - String ensId = gRsc.getEnsembleIdRaw(); - if ((ensId != null) && (ensId.length() > 1)) { - currColor = ChosenSREFColors.getInstance() - .getGradientByEnsembleId(ensId); - rsc.getCapability(ColorableCapability.class) - .setColor(currColor.getRGB()); - rsc.issueRefresh(); - } - } - } - ensemblesTreeViewer.refresh(); - } - - updateCursor(normalCursor); - + setPerturbationMembers(getEnsembleMemberGenericResources(ensembleName)); } }); + // TODO: poor-man's way of knowing what type of flavor this ensemble is + if ((ensembleName.indexOf("GEFS") >= 0) + || (ensembleName.indexOf("GFS Ensemble") >= 0)) { + updateGEFSEnsembleColors(ensembleName, getPerturbationMembers()); + } else if (ensembleName.indexOf("SREF") >= 0) { + updateSREFColors(ensembleName, getPerturbationMembers()); + } + } + + private void updateSREFColors(String ensembleName, + final List children) { + + EnsembleSREFColorChooser cd = new EnsembleSREFColorChooser( + owner.getShell()); + cd.setBlockOnOpen(true); + if (cd.open() == Window.OK) { + + cd.close(); + + SREFMembersColorChangeJob ccj = new SREFMembersColorChangeJob( + "Changing SREF Ensemble Members Colors"); + ccj.setPriority(Job.INTERACTIVE); + /** + * If we don't pounce on this Job (i.e. by delaying a little) then + * (for reasons still unknown) the UI thread clears the dialog box + * and repaints the once occluded display more immediately. + */ + ccj.schedule(elegantWaitPeriod); + + } + } private void updateGEFSEnsembleColors(String ensembleName, final List children) { - VizApp.runSync(new Runnable() { + EnsembleGEFSColorChooser cd = new EnsembleGEFSColorChooser( + owner.getShell()); + cd.setBlockOnOpen(true); + if (cd.open() == Window.OK) { + cd.close(); - @Override - public void run() { - - updateCursor(waitCursor); - - EnsembleGEFSColorChooser cd = new EnsembleGEFSColorChooser( - owner.getShell()); - cd.setBlockOnOpen(true); - if (cd.open() == Window.OK) { - - // EditorUtil.getActiveEditor(). - Color currColor = null; - int count = 0; - for (GenericResourceHolder gRsc : children) { - if (gRsc instanceof GridResourceHolder) { - count++; - AbstractVizResource rsc = gRsc.getRsc(); - if (count == 1) { - currentEnsembleRsc = rsc; - } - String ensId = gRsc.getEnsembleIdRaw(); - if ((ensId != null) && (ensId.length() > 1)) { - currColor = ChosenGEFSColors.getInstance() - .getGradientByEnsembleId(ensId); - rsc.getCapability(ColorableCapability.class) - .setColor(currColor.getRGB()); - rsc.getCapability(OutlineCapability.class); - rsc.issueRefresh(); - } - } - } - ensemblesTreeViewer.refresh(); - } - - updateCursor(normalCursor); - } - - }); + GEFSMembersColorChangeJob ccj = new GEFSMembersColorChangeJob( + "Changing GEFS Ensemble Members Colors"); + ccj.setPriority(Job.INTERACTIVE); + /** + * If we don't pounce on this Job (i.e. by delaying a little) then + * (for reasons still unknown) the UI thread clears the dialog box + * and repaints the once occluded display more immediately. + */ + ccj.schedule(elegantWaitPeriod); + } } @@ -2149,41 +2157,12 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { return childResources; } - protected List> getEnsembleMemberVizResources( - String ensembleName) { - - TreeItem parentItem = findTreeItemByLabelName(ensembleName); - - List descendants = new ArrayList(); - getAllDescendants(parentItem, descendants); - - List> childResources = new ArrayList>(); - - // TODO: this test is due to the fact that for some reason the - // ensemble children aren't being initially recognized as actual - // members of the ensemble root TreeItem even though they are - // really there! - - if (descendants.size() > 0) { - for (TreeItem ti : descendants) { - Object data = ti.getData(); - if (data == null) { - continue; - } - if (data instanceof GenericResourceHolder) { - GenericResourceHolder gr = (GenericResourceHolder) data; - AbstractVizResource rsc = gr.getRsc(); - childResources.add(rsc); - } - } - } - return childResources; - } - public void prepareForNewToolInput() { VizApp.runAsync(new Runnable() { public void run() { - ensemblesTreeViewer.setInput(null); + if (isViewerTreeReady()) { + ensemblesTreeViewer.setInput(null); + } } }); } @@ -2200,9 +2179,10 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { service.addPartListener(viewPartListener); } - if ((!EnsembleToolManager.getInstance().isReady()) - && (ensemblesTreeViewer != null)) { - ensemblesTreeViewer.refresh(false); + if (!EnsembleToolManager.getInstance().isReady()) { + if (isViewerTreeReady()) { + ensemblesTreeViewer.refresh(false); + } return; } @@ -2210,8 +2190,9 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { @SuppressWarnings("unchecked") public void run() { - if (ensemblesTreeViewer == null) + if (ensemblesTreeViewer == null || !isViewerTreeReady()) { return; + } Map> ensembleResourcesMap = EnsembleToolManager .getInstance().getEnsembleResources(); @@ -2242,8 +2223,7 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { GenericResourceHolder grh = null; String ensembleRscName = null; - if ((ensemblesTreeViewer != null) - && (EnsembleToolManager.getInstance().isReady())) { + if (isViewerTreeReady()) { TreeItem[] selectedItems = ensembleTree.getSelection(); if ((selectedItems != null) && (selectedItems.length > 0) @@ -2278,8 +2258,11 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { } } } - // always refresh the tree ... - ensemblesTreeViewer.refresh(true); + + /* always refresh the tree */ + if (isViewerTreeReady()) { + ensemblesTreeViewer.refresh(true); + } } } @@ -2335,13 +2318,16 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { private List getTreeExpansion() { List expandedItems = new ArrayList(); - TreeItem[] children = ensemblesTreeViewer.getTree().getItems(); - List immediateChildren = Arrays.asList(children); - for (TreeItem ti : immediateChildren) { - if (ti.getData() instanceof String) { - String s = (String) ti.getData(); - if (ti.getExpanded()) { - expandedItems.add(s); + + if (isViewerTreeReady()) { + TreeItem[] children = ensemblesTreeViewer.getTree().getItems(); + List immediateChildren = Arrays.asList(children); + for (TreeItem ti : immediateChildren) { + if (ti.getData() instanceof String) { + String s = (String) ti.getData(); + if (ti.getExpanded()) { + expandedItems.add(s); + } } } } @@ -3480,19 +3466,201 @@ public class EnsembleToolViewer extends ViewPart implements IRefreshListener { @Override public void refresh() { - if ((ensemblesTreeViewer != null) - && (EnsembleToolManager.getInstance().isReady())) { - VizApp.runAsync(new Runnable() { - @Override - public void run() { + VizApp.runAsync(new Runnable() { + @Override + public void run() { + if (isViewerTreeReady()) { ensemblesTreeViewer.refresh(true); } - }); + } + }); + } + + protected void updateCursor(final Cursor c) { + VizApp.runSync(new Runnable() { + + @Override + public void run() { + if (isViewerTreeReady()) { + ensembleTree.setCursor(c); + } + } + + }); + } + + /* + * Allow user to change SREF member colors based on a chosen color pattern + * map. + */ + protected class SREFMembersColorChangeJob extends Job { + + public SREFMembersColorChangeJob(String name) { + super(name); } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IStatus status = null; + + Color currColor = null; + for (GenericResourceHolder gRsc : getPerturbationMembers()) { + if (gRsc instanceof GridResourceHolder) { + AbstractVizResource rsc = gRsc.getRsc(); + String ensId = gRsc.getEnsembleIdRaw(); + if ((ensId != null) && (ensId.length() > 1)) { + currColor = ChosenSREFColors.getInstance() + .getGradientByEnsembleId(ensId); + rsc.getCapability(ColorableCapability.class).setColor( + currColor.getRGB()); + } + } + } + status = Status.OK_STATUS; + + return status; + } + } - private void updateCursor(Cursor c) { - ensembleTree.setCursor(c); + /* + * Allow user to change GEFS member colors based on a chosen color pattern + * map. + */ + protected class GEFSMembersColorChangeJob extends Job { + + public GEFSMembersColorChangeJob(String name) { + super(name); + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IStatus status = null; + + Color currColor = null; + int count = 0; + + for (GenericResourceHolder gRsc : getPerturbationMembers()) { + if (gRsc instanceof GridResourceHolder) { + count++; + AbstractVizResource rsc = gRsc.getRsc(); + if (count == 1) { + currentEnsembleRsc = rsc; + } + String ensId = gRsc.getEnsembleIdRaw(); + if ((ensId != null) && (ensId.length() > 1)) { + currColor = ChosenGEFSColors.getInstance() + .getGradientByEnsembleId(ensId); + rsc.getCapability(ColorableCapability.class).setColor( + currColor.getRGB()); + rsc.getCapability(OutlineCapability.class); + } + } + } + + status = Status.OK_STATUS; + return status; + } + } + /* + * This job toggles visibility for an individual product or ensemble member. + */ + protected class ToggleProductVisiblityJob extends Job { + + private TreeItem treeItem = null; + + public ToggleProductVisiblityJob(String name) { + super(name); + } + + public void setTargetTreeItem(TreeItem ti) { + treeItem = ti; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IStatus status = null; + + if (treeItem == null) { + status = Status.CANCEL_STATUS; + } else { + toggleItemVisible(treeItem); + + /* + * if this was the last item to be toggled off, for example, in + * an ensemble group, then you need to toggle the parent tree + * item off also + */ + + matchParentToChildrenVisibility(treeItem); + + status = Status.OK_STATUS; + } + return status; + } + + } + + /* + * This job toggles visibility for all members of an ensemble product. + */ + protected class ToggleEnsembleVisiblityJob extends Job { + + private String ensembleName = null; + + public ToggleEnsembleVisiblityJob(String name) { + super(name); + } + + public void setTargetEnsembleProduct(String en) { + ensembleName = en; + } + + @Override + protected IStatus run(IProgressMonitor monitor) { + IStatus status = null; + + if (ensembleName == null) { + status = Status.CANCEL_STATUS; + } else { + updateCursor(waitCursor); + TreeItem ensembleRootItem = findTreeItemByLabelName(ensembleName); + TreeItem[] descendants = getDirectDescendants(ensembleRootItem); + TreeItem ti = null; + int numDescendants = descendants.length; + for (int i = 0; i < numDescendants; i++) { + ti = descendants[i]; + toggleItemVisible(ti); + + if (i == numDescendants - 1) { + /* + * if this was the last item to be toggled off, for + * example, in an ensemble group, then you need to + * toggle the parent tree item off also + */ + matchParentToChildrenVisibility(ti); + } + + } + updateCursor(normalCursor); + status = Status.OK_STATUS; + } + return status; + } + + } + + private boolean isViewerTreeReady() { + boolean isReady = false; + + if (ensemblesTreeViewer != null && ensembleTree != null + && ensemblesTreeViewer.getTree() != null + && !ensemblesTreeViewer.getTree().isDisposed()) { + isReady = true; + } + + return isReady; + } } From 2525e00fb2367040a70800c4fd9157c8c86ef6b6 Mon Sep 17 00:00:00 2001 From: Ana Rivera Date: Mon, 23 Feb 2015 17:27:56 +0000 Subject: [PATCH 22/34] VLab Issue #6566 - HTI: Update six scripts and one image; fixes #6566 Change-Id: I49052579740e22c54c0307d3787d9656bdf30bb5 Former-commit-id: d828b762ec42667d8044688b3406fd426ee52d88 [formerly 2f9dd9b8422b899146f73da0a3d922b32b021152] Former-commit-id: ccc338d7da580ccfe134facc6031699cdf4c73c9 --- .../gfe/userPython/gfeConfig/TornadoThreat.py | 2 +- .../gfe/userPython/gfeConfig/gfeConfig.py | 2 +- .../procedures/TCImpactGraphics_KML.py | 2 +- .../procedures/TCStormSurgeThreat.py | 4 ++-- .../hti/bin/kml_legend.sh | 4 ++-- .../hti/bin/logos.png | Bin .../hti/bin/make_hti.sh | 18 ++++++++++-------- 7 files changed, 17 insertions(+), 15 deletions(-) mode change 100644 => 100755 edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/logos.png mode change 100755 => 100644 edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/TornadoThreat.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/TornadoThreat.py index 2e42a6cea4..ea4aa0966e 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/TornadoThreat.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/TornadoThreat.py @@ -53,7 +53,7 @@ Lakes_graphicColor = 'blue' # MAP BACKGROUNDS #MapBackgrounds_default = ['Counties','Marine_Zones_XXX','Interstates'] MapBackgrounds_default = ['Zones_XXX','Marine_Zones_XXX','Interstates','States','Lakes'] -XXX_mask = "XXX_CWA" +XXX_mask = "XXX" DefaultSamples = ['XXXTornadoThreat'] # Customize FONT SIZES here diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/gfeConfig.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/gfeConfig.py index b4bc9c54e4..61631f654a 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/gfeConfig.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/gfeConfig/gfeConfig.py @@ -1679,7 +1679,7 @@ Scripts = [ "-d {productDB} ", "Make and Send HTI:" + - "xterm -e ssh px2f /awips2/GFESuite/hti/bin/make_hti.sh", + "xterm -e ssh px2f /awips2/GFESuite/hti/bin/make_hti.sh {site}", "Official Grids to LDAD: " + "ifpAG -h {host} -r {port} -o - -d {productDB} | gzip -9 > " + diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCImpactGraphics_KML.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCImpactGraphics_KML.py index d3c274bb0e..f1ad2a8931 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCImpactGraphics_KML.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCImpactGraphics_KML.py @@ -213,7 +213,7 @@ class Procedure (SmartScript.SmartScript): elif threatWEName == "FloodingRainThreat": editArea = self.getEditArea("MFL") elif threatWEName == "TornadoThreat": - editArea = self.getEditArea("MFL_CWA") + editArea = self.getEditArea("MFL") else: editArea = self.getEditArea("Marinezones") diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py index 6235a0d14e..d6ca7724cf 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCStormSurgeThreat.py @@ -43,7 +43,7 @@ VariableList = [("DEFAULT: Typical. Should only be changed in coordination with "Higher (40% Exceedance; for well-behaved systems within 6 hours of the event)", "Highest (50% Exceedance; for well-behaved systems at time of the event)"]), ("Grid Smoothing?", "Yes", "radio", ["Yes","No"]), - ("Make grids from PHISH\n or ICS?\n", "PHISH", "radio", ["PHISH", "ISC"]), + ("Make grids from PHISH\n or ISC?\n", "PHISH", "radio", ["PHISH", "ISC"]), ] class Procedure (SmartScript.SmartScript): @@ -432,7 +432,7 @@ class Procedure (SmartScript.SmartScript): # List of elements # See if we should copy from ISC. If so, do the copy and exit smoothThreatGrid = varDict["Grid Smoothing?"] - PHISHorISC = varDict["Make grids from PHISH\n or ICS?\n"] + PHISHorISC = varDict["Make grids from PHISH\n or ISC?\n"] #PHISHorISC = "PHISH" topodb = "NED" #topodb = varDict["Topographic Database?"] diff --git a/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/kml_legend.sh b/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/kml_legend.sh index 3567115202..7dc0d331dd 100755 --- a/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/kml_legend.sh +++ b/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/kml_legend.sh @@ -12,6 +12,7 @@ # and inland threats # UPDATED: 11 Sept 2014 - JCM - completed updates above # UPDATED: 20 Oct. 2014 - JCM - set up for 2015 season +# UPDATED: 18 Feb. 2015 - JCM - added full path for logos.png # ######################################################################## # CONFIGURATION SECTION BELOW @@ -41,7 +42,7 @@ convert -size 200x500 xc:black temp.png convert temp.png null: -matte -compose Clear -composite -compose Over transparent.png # insert the logos at the bottom -composite -gravity south -geometry +0+0 logos.png transparent.png trans2.png +composite -gravity south -geometry +0+0 ${HTI_HOME}/bin/logos.png transparent.png trans2.png # write the date onto the image DATE=`date +"Issued %F %H%MZ"` @@ -81,7 +82,6 @@ convert trans2b.png -font Century-Schoolbook-Bold -pointsize 20 -fill black -ann convert trans2b.png -font Century-Schoolbook-Bold -pointsize 20 -fill black -annotate +5+250 "Threat" \ -annotate +5+230 "Wind" windthreatlegend.png -#echo ${PRODUCTdir} chmod 666 *legend.png mv *legend.png ${PRODUCTdir}/ diff --git a/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/logos.png b/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/logos.png old mode 100644 new mode 100755 diff --git a/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh b/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh old mode 100755 new mode 100644 index 6ee7033270..53daececc9 --- a/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh +++ b/edexOsgi/com.raytheon.uf.tools.gfesuite/hti/bin/make_hti.sh @@ -5,7 +5,7 @@ # Shell Used: BASH shell # Original Author(s): Douglas.Gaer@noaa.gov # File Creation Date: 01/27/2009 -# Date Last Modified: 10/20/2014 +# Date Last Modified: 02/20/2015 # # Contributors: # Joe Maloney (MFL), Pablo Santos (MFL) @@ -33,6 +33,9 @@ # History: # 20 OCT 2014 - jcm - created from make_ghls.sh, broke out of webapps # package. Renamed make_hti.sh. +# 20 FEB 2015 - jcm - modified ifpIMAGE line to use ssh -x px2f; fixed +# LOG_FILE at end of script; corrected $site variable +# for kml rsync. # ######################################################################## # CHECK TO SEE IF SITE ID WAS PASSED AS ARGUMENT @@ -84,7 +87,7 @@ fi # Log file header echo " " >> $LOG_FILE echo "####################################################################################" >> $LOG_FILE -echo "# Starting Make_HTI Script.... #" >> $LOG_FILE +echo "# Starting Make_HTI Script.... #" >> $LOG_FILE echo "####################################################################################" >> $LOG_FILE chmod 666 $LOG_FILE @@ -112,7 +115,7 @@ for PARM in $PARMS do # NOTE: cannot run ifpIMAGE on dx3/dx4 - must ssh to a px echo "Creating ${PARM} image..." >> $LOG_FILE - ssh px1 "unset DISPLAY; ${GFEBINdir}/ifpIMAGE -site ${SITE} -c ${PARM} -o ${PRODUCTdir}" + ssh -x px2f "unset DISPLAY; ${GFEBINdir}/ifpIMAGE -site ${SITE} -c ${PARM} -o ${PRODUCTdir}" convert ${PRODUCTdir}/${SITE}${PARM}.png -resize 104x148 ${PRODUCTdir}/${SITE}${PARM}_sm.png done @@ -120,8 +123,7 @@ rm -f ${PRODUCTdir}/*.info # Generate KML automatically via runProcedure echo "Running KML procedure." >> $LOG_FILE -#ssh px1 "unset DISPLAY; ${GFEBINdir}/runProcedure -site ${SITE} -n TCImpactGraphics_KML -c gfeConfig" -ssh px1 "unset DISPLAY; ${GFEBINdir}/runProcedure -site ${SITE} -n TCImpactGraphics_KML2015 -c gfeConfig" +ssh -x px2f "unset DISPLAY; ${GFEBINdir}/runProcedure -site ${SITE} -n TCImpactGraphics_KML -c gfeConfig" # Create legends for KML ${HTI_HOME}/bin/kml_legend.sh @@ -139,7 +141,7 @@ echo "/usr/bin/ssh -o stricthostkeychecking=no -x ${LDADuser}@${LDADserver} ${CM /usr/bin/ssh -o stricthostkeychecking=no -x ${LDADuser}@${LDADserver} ${CMD} echo "Copying KML.TXT files to NWSHQ Web farm ${NWSHQ_RSYNCSERVER}" >> $LOG_FILE -CMD="/usr/bin/rsync -av --force --progress --stats ${LDAD_DATA}/*.txt ${NWSHQ_RSYNCSERVER}::ghls_includes/${siteid}" +CMD="/usr/bin/rsync -av --force --progress --stats ${LDAD_DATA}/*.txt ${NWSHQ_RSYNCSERVER}::ghls_includes/${site}" echo "/usr/bin/ssh -o stricthostkeychecking=no -x ${LDADuser}@${LDADserver} ${CMD}" >> $LOG_FILE /usr/bin/ssh -o stricthostkeychecking=no -x ${LDADuser}@${LDADserver} "${CMD}" echo "Copy to ${NWSHQ_RSYNCSERVER} complete" >> $LOG_FILE @@ -193,7 +195,7 @@ else echo "**** ${HTI_HOME}/etc/sitevars.${site}" >> $LOG_FILE fi -echo "Script complete at $(date)" >> ${LOGfile} -echo " " >> ${LOGfile} +echo "Script complete at $(date)" >> ${LOG_FILE} +echo " " >> ${LOG_FILE} exit 0 ######################################################################## From 34bc29be410cd60f9f17d2ad02eaa4072fc2f4e2 Mon Sep 17 00:00:00 2001 From: Ron Anderson Date: Mon, 9 Feb 2015 11:41:17 -0600 Subject: [PATCH 23/34] Issue #4099 Fix ETNs for products that span year-end. (cherry picked from commit c7a8c9b049ae8d73ffb61765f31374e01d1d2a33) Former-commit-id: ebb221d935c48f628db85cfbcc11f50116177b9f [formerly 7ca4dd9ba059035e366807d4928768121fdd0712] Former-commit-id: e19901c6e12dabd87795ef64524c2e275d6bf8c0 --- .../textUtilities/headline/HazardsTable.py | 3 ++- .../build.edex/esb/conf/logback-ingest.xml | 2 +- .../uf/edex/activetable/ActiveTable.java | 19 ++++++++++--------- .../common_static/base/vtec/ActiveTable.py | 3 ++- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py index 4fa223d733..8e76988684 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py @@ -35,6 +35,7 @@ # 02/05/14 2774 dgilling Fix error logging statements in # __warnETNduplication() and # __highestETNActiveTable. +# 02/05/15 4099 randerso Fixed exception handling in __getActiveTable # @@ -932,7 +933,7 @@ class HazardsTable(VTECTableUtil.VTECTableUtil): except: self.log.exception("Unable to access VTEC Active Table: ") - raise Exception, s + raise def __createCityHazards(self): if not self.__accurateCities: diff --git a/edexOsgi/build.edex/esb/conf/logback-ingest.xml b/edexOsgi/build.edex/esb/conf/logback-ingest.xml index 3b9c5a57ec..ddd0efca77 100644 --- a/edexOsgi/build.edex/esb/conf/logback-ingest.xml +++ b/edexOsgi/build.edex/esb/conf/logback-ingest.xml @@ -71,7 +71,7 @@ - + ${edex.home}/logs/edex-${edex.run.mode}-activeTableChange-%d{yyyyMMdd}.log 30 diff --git a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java index 459ca8251b..8f821b2ed6 100644 --- a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java +++ b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java @@ -55,9 +55,9 @@ import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.PerformanceStatus; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.common.status.UFStatus.Priority; -import com.raytheon.uf.common.util.CollectionUtil; import com.raytheon.uf.common.time.util.ITimer; import com.raytheon.uf.common.time.util.TimeUtil; +import com.raytheon.uf.common.util.CollectionUtil; import com.raytheon.uf.common.util.FileUtil; import com.raytheon.uf.edex.core.EDEXUtil; import com.raytheon.uf.edex.database.DataAccessLayerException; @@ -96,6 +96,7 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery; * Pass issuance site id to getActiveTable() * in updateActiveTable() so records will * be updated correctly. + * Feb 05, 2015 4099 randerso Fixed latest ETN query for year-end * * * @@ -359,14 +360,14 @@ public class ActiveTable { PythonScript python = threadLocalPythonScript.get(); try { result = (MergeResult) python.execute("mergeFromJava", args); - } catch (JepException e) { - statusHandler.handle(Priority.PROBLEM, - "Error updating active table", e); - } - } catch (Exception e) { + } catch (JepException e) { statusHandler.handle(Priority.PROBLEM, - "Error initializing active table python", e); + "Error updating active table", e); } + } catch (Exception e) { + statusHandler.handle(Priority.PROBLEM, + "Error initializing active table python", e); + } return result; } @@ -433,8 +434,8 @@ public class ActiveTable { if (latestEtn && currentTime != null) { Calendar yearStart = Calendar.getInstance(); yearStart.set(currentTime.get(Calendar.YEAR), Calendar.JANUARY, - 0, 0, 0); - query.addQueryParam("startTime", yearStart, "greater_than"); + 1, 0, 0); + query.addQueryParam("issueTime", yearStart, "greater_than"); query.addOrder("etn", false); query.setMaxResults(1); } diff --git a/edexOsgi/com.raytheon.uf.edex.activetable/utility/common_static/base/vtec/ActiveTable.py b/edexOsgi/com.raytheon.uf.edex.activetable/utility/common_static/base/vtec/ActiveTable.py index 0105bf91b6..9a06bf3a6a 100644 --- a/edexOsgi/com.raytheon.uf.edex.activetable/utility/common_static/base/vtec/ActiveTable.py +++ b/edexOsgi/com.raytheon.uf.edex.activetable/utility/common_static/base/vtec/ActiveTable.py @@ -31,6 +31,7 @@ # 06/17/13 #3296 randerso Moved active table backup and purging # to a separate thread in java. # Added performance logging +# 02/05/15 #4099 randerso Changed log level of year-end issuance tweak # import time @@ -119,7 +120,7 @@ class ActiveTable(VTECTableUtil.VTECTableUtil): if oldYear < newYear: if (newR['act'] == "EXP" and newR['endTime'] == oldR['endTime']) or \ self.__overlaps((oldR['startTime'],oldR['endTime']), (newR['startTime'],newR['endTime'])): - LogStream.logVerbose("Reset issuance time to last year:", + LogStream.logEvent("Reset issuance time to last year:", "\nNewRec: ", self.printEntry(newR), "OldRec: ", self.printEntry(oldR)) newR['issueTime'] = lastYearIssueTime From ab7e4138934d1d31a2e06a47d4ed4238b2ab86e2 Mon Sep 17 00:00:00 2001 From: David Gillingham Date: Tue, 24 Feb 2015 12:39:03 -0600 Subject: [PATCH 24/34] Issue #4127: Add ClusterTask-backed write lock to ActiveTable. Change-Id: I54309f369e7ef872fd102a764641569d95c62799 Former-commit-id: def338c49fa20e82220c7a2d4006381d83c7b402 [formerly c564462d34bae64c3339339254d5cb6caba70fda] Former-commit-id: 34ea2453b8245e715bba14a0a10ef490e820b36b --- .../uf/edex/activetable/ActiveTable.java | 268 +++++++++++------- .../uf/edex/database/dao/CoreDao.java | 23 ++ 2 files changed, 184 insertions(+), 107 deletions(-) diff --git a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java index 8f821b2ed6..32f1b8adf6 100644 --- a/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java +++ b/edexOsgi/com.raytheon.uf.edex.activetable/src/com/raytheon/uf/edex/activetable/ActiveTable.java @@ -61,6 +61,10 @@ import com.raytheon.uf.common.util.CollectionUtil; import com.raytheon.uf.common.util.FileUtil; import com.raytheon.uf.edex.core.EDEXUtil; import com.raytheon.uf.edex.database.DataAccessLayerException; +import com.raytheon.uf.edex.database.cluster.ClusterLockUtils; +import com.raytheon.uf.edex.database.cluster.ClusterLockUtils.LockState; +import com.raytheon.uf.edex.database.cluster.ClusterTask; +import com.raytheon.uf.edex.database.cluster.handler.CurrentTimeClusterLockHandler; import com.raytheon.uf.edex.database.dao.CoreDao; import com.raytheon.uf.edex.database.dao.DaoConfig; import com.raytheon.uf.edex.database.query.DatabaseQuery; @@ -97,6 +101,8 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery; * in updateActiveTable() so records will * be updated correctly. * Feb 05, 2015 4099 randerso Fixed latest ETN query for year-end + * Feb 23, 2015 4127 dgilling Use cluster locking to only allow 1 active + * table write at a time. * * * @@ -111,6 +117,10 @@ public class ActiveTable { private static final Logger changeLog = Logger .getLogger("ActiveTableChange"); + private static final String ACTIVE_TABLE_LOCK_NAME = "ActiveTableWriteLock"; + + private static final long DEFAULT_LOCK_TIMEOUT = 5 * TimeUtil.MILLIS_PER_MINUTE; + private static ThreadLocal threadLocalPythonScript = new ThreadLocal() { /* @@ -285,7 +295,7 @@ public class ActiveTable { */ private void updateActiveTable(String siteId, List newRecords, float offsetSecs) { - if (newRecords.size() > 0) { + if (!newRecords.isEmpty()) { ActiveTableMode mode = ActiveTableMode.PRACTICE; if (newRecords.get(0) instanceof OperationalActiveTableRecord) { mode = ActiveTableMode.OPERATIONAL; @@ -296,36 +306,67 @@ public class ActiveTable { IPerformanceStatusHandler perfStat = PerformanceStatus .getHandler("ActiveTable"); ITimer timer = TimeUtil.getTimer(); - timer.start(); - List activeTable = getActiveTable(issueSiteId, mode); - timer.stop(); - perfStat.logDuration("getActiveTable", timer.getElapsedTime()); + MergeResult result = null; + ClusterTask writeLock = null; + try { + boolean logFirst = true; + timer.start(); + do { + if (logFirst) { + statusHandler + .info("updateActiveTable() waiting on lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + + mode.toString() + "]."); + logFirst = false; + } + writeLock = ClusterLockUtils.lock(ACTIVE_TABLE_LOCK_NAME, + mode.toString(), new CurrentTimeClusterLockHandler( + DEFAULT_LOCK_TIMEOUT, false), true); + } while (!writeLock.getLockState().equals(LockState.SUCCESSFUL)); + statusHandler + .info("updateActiveTable() obtained lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + + mode.toString() + "]."); + timer.stop(); + perfStat.logDuration("getLock", timer.getElapsedTime()); - // get decoder sites to see if we need to backup active table - Set decoderSites = getDecoderSites(siteId); - - // if any new record is from one of the decoder sites - // we need to queue a backup - for (ActiveTableRecord rec : newRecords) { - if (decoderSites.contains(rec.getOfficeid())) { - ActiveTableBackup.queue(mode, activeTable); - break; + timer.reset(); + timer.start(); + List activeTable = getActiveTable( + issueSiteId, mode); + timer.stop(); + perfStat.logDuration("getActiveTable", timer.getElapsedTime()); + // get decoder sites to see if we need to backup active table + Set decoderSites = getDecoderSites(siteId); + // if any new record is from one of the decoder sites + // we need to queue a backup + for (ActiveTableRecord rec : newRecords) { + if (decoderSites.contains(rec.getOfficeid())) { + ActiveTableBackup.queue(mode, activeTable); + break; + } + } + timer.reset(); + timer.start(); + result = filterTable(siteId, activeTable, newRecords, mode, + offsetSecs); + timer.stop(); + perfStat.logDuration("filterTable", timer.getElapsedTime()); + timer.reset(); + timer.start(); + updateTable(siteId, result, mode); + timer.stop(); + perfStat.logDuration("updateTable", timer.getElapsedTime()); + } finally { + if (writeLock != null) { + statusHandler.info("updateActiveTable() released lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + mode.toString() + + "]."); + ClusterLockUtils.unlock(writeLock, true); } } - timer.reset(); - timer.start(); - MergeResult result = filterTable(siteId, activeTable, newRecords, - mode, offsetSecs); - timer.stop(); - perfStat.logDuration("filterTable", timer.getElapsedTime()); - - timer.reset(); - timer.start(); - updateTable(siteId, result, mode); - timer.stop(); - perfStat.logDuration("updateTable", timer.getElapsedTime()); - if (result.changeList.size() > 0) { + if (!result.changeList.isEmpty()) { sendNotification(mode, result.changeList, "VTECDecoder"); } } @@ -403,54 +444,52 @@ public class ActiveTable { ActiveTableMode mode, String phensigList, String action, String etn, Calendar currentTime, boolean requestValidTimes, boolean latestEtn) { - synchronized (ActiveTable.class) { - DatabaseQuery query = null; - CoreDao dao = null; + DatabaseQuery query = null; + CoreDao dao = null; - if (mode.equals(ActiveTableMode.OPERATIONAL)) { - query = new DatabaseQuery(OperationalActiveTableRecord.class); - dao = operationalDao; - } else { - query = new DatabaseQuery(PracticeActiveTableRecord.class); - dao = practiceDao; - } - - if (phensigList != null) { - query.addQueryParam("phensig", phensigList, "in"); - } - - if (action != null) { - query.addQueryParam("act", action, "in"); - } - - if (etn != null) { - query.addQueryParam("etn", etn, "in"); - } - - if (requestValidTimes && currentTime != null) { - // Current Time - query.addQueryParam("endTime", currentTime, "greater_than"); - } - if (latestEtn && currentTime != null) { - Calendar yearStart = Calendar.getInstance(); - yearStart.set(currentTime.get(Calendar.YEAR), Calendar.JANUARY, - 1, 0, 0); - query.addQueryParam("issueTime", yearStart, "greater_than"); - query.addOrder("etn", false); - query.setMaxResults(1); - } - - query.addQueryParam("officeid", siteId, "in"); - - List result = null; - try { - result = (List) dao.queryByCriteria(query); - } catch (DataAccessLayerException e) { - statusHandler.handle(Priority.PROBLEM, - "Error querying active table for site " + siteId, e); - } - return result; + if (mode.equals(ActiveTableMode.OPERATIONAL)) { + query = new DatabaseQuery(OperationalActiveTableRecord.class); + dao = operationalDao; + } else { + query = new DatabaseQuery(PracticeActiveTableRecord.class); + dao = practiceDao; } + + if (phensigList != null) { + query.addQueryParam("phensig", phensigList, "in"); + } + + if (action != null) { + query.addQueryParam("act", action, "in"); + } + + if (etn != null) { + query.addQueryParam("etn", etn, "in"); + } + + if (requestValidTimes && currentTime != null) { + // Current Time + query.addQueryParam("endTime", currentTime, "greater_than"); + } + if (latestEtn && currentTime != null) { + Calendar yearStart = Calendar.getInstance(); + yearStart.set(currentTime.get(Calendar.YEAR), Calendar.JANUARY, 1, + 0, 0); + query.addQueryParam("issueTime", yearStart, "greater_than"); + query.addOrder("etn", false); + query.setMaxResults(1); + } + + query.addQueryParam("officeid", siteId, "in"); + + List result = null; + try { + result = (List) dao.queryByCriteria(query); + } catch (DataAccessLayerException e) { + statusHandler.handle(Priority.PROBLEM, + "Error querying active table for site " + siteId, e); + } + return result; } /** @@ -463,23 +502,12 @@ public class ActiveTable { */ private static void updateTable(String siteId, MergeResult changes, ActiveTableMode mode) { - synchronized (ActiveTable.class) { - List updated = changes.updatedList; - List purged = changes.purgedList; + List updated = changes.updatedList; + List purged = changes.purgedList; - CoreDao dao = null; - if (mode.equals(ActiveTableMode.OPERATIONAL)) { - dao = operationalDao; - } else { - dao = practiceDao; - } - for (ActiveTableRecord update : updated) { - dao.saveOrUpdate(update); - } - for (ActiveTableRecord delete : purged) { - dao.delete(delete); - } - } + CoreDao dao = (ActiveTableMode.OPERATIONAL.equals(mode)) ? operationalDao + : practiceDao; + dao.bulkSaveOrUpdateAndDelete(updated, purged); } /** @@ -550,22 +578,23 @@ public class ActiveTable { ActiveTableMode tableName, List newRecords, float timeOffset, boolean makeBackup, boolean runIngestAT, String xmlSource) throws JepException { + String scriptName = runIngestAT ? "ingestAT.py" : "MergeVTEC.py"; + IPathManager pathMgr = PathManagerFactory.getPathManager(); + LocalizationContext commonCx = pathMgr.getContext( + LocalizationType.COMMON_STATIC, LocalizationLevel.BASE); + String scriptPath = pathMgr.getFile(commonCx, + FileUtil.join(ActiveTablePyIncludeUtil.VTEC, scriptName)) + .getPath(); + String pythonIncludePath = PyUtil.buildJepIncludePath( + ActiveTablePyIncludeUtil.getCommonPythonIncludePath(), + ActiveTablePyIncludeUtil.getVtecIncludePath(siteId), + ActiveTablePyIncludeUtil.getGfeConfigIncludePath(siteId), + ActiveTablePyIncludeUtil.getIscScriptsIncludePath()); + MergeResult result = null; PythonScript script = null; + ClusterTask writeLock = null; try { - String scriptName = runIngestAT ? "ingestAT.py" : "MergeVTEC.py"; - IPathManager pathMgr = PathManagerFactory.getPathManager(); - LocalizationContext commonCx = pathMgr.getContext( - LocalizationType.COMMON_STATIC, LocalizationLevel.BASE); - String scriptPath = pathMgr.getFile(commonCx, - FileUtil.join(ActiveTablePyIncludeUtil.VTEC, scriptName)) - .getPath(); - String pythonIncludePath = PyUtil.buildJepIncludePath( - ActiveTablePyIncludeUtil.getCommonPythonIncludePath(), - ActiveTablePyIncludeUtil.getVtecIncludePath(siteId), - ActiveTablePyIncludeUtil.getGfeConfigIncludePath(siteId), - ActiveTablePyIncludeUtil.getIscScriptsIncludePath()); - try { script = new PythonScript(scriptPath, pythonIncludePath, ActiveTable.class.getClassLoader()); @@ -575,6 +604,23 @@ public class ActiveTable { throw e; } + boolean logFirst = true; + do { + if (logFirst) { + statusHandler.info("mergeRemoteTable() waiting on lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + + tableName.toString() + "]."); + logFirst = false; + } + writeLock = ClusterLockUtils.lock(ACTIVE_TABLE_LOCK_NAME, + tableName.toString(), + new CurrentTimeClusterLockHandler(DEFAULT_LOCK_TIMEOUT, + false), true); + } while (!writeLock.getLockState().equals(LockState.SUCCESSFUL)); + statusHandler.info("mergeRemoteTable() obtained lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + tableName.toString() + + "]."); + try { String site4Char = SiteMap.getInstance().getSite4LetterId( siteId); @@ -597,18 +643,26 @@ public class ActiveTable { "Error merging active table", e); throw e; } + + if (result != null) { + updateTable(siteId, result, tableName); + } } finally { + if (writeLock != null) { + statusHandler.info("mergeRemoteTable() released lock [" + + ACTIVE_TABLE_LOCK_NAME + ":" + tableName.toString() + + "]."); + ClusterLockUtils.unlock(writeLock, true); + } + if (script != null) { script.dispose(); script = null; } } - if (result != null) { - updateTable(siteId, result, tableName); - if (!result.changeList.isEmpty()) { - sendNotification(tableName, result.changeList, "MergeVTEC"); - } + if ((result != null) && (!result.changeList.isEmpty())) { + sendNotification(tableName, result.changeList, "MergeVTEC"); } } diff --git a/edexOsgi/com.raytheon.uf.edex.database/src/com/raytheon/uf/edex/database/dao/CoreDao.java b/edexOsgi/com.raytheon.uf.edex.database/src/com/raytheon/uf/edex/database/dao/CoreDao.java index 943b272171..dde8965879 100644 --- a/edexOsgi/com.raytheon.uf.edex.database/src/com/raytheon/uf/edex/database/dao/CoreDao.java +++ b/edexOsgi/com.raytheon.uf.edex.database/src/com/raytheon/uf/edex/database/dao/CoreDao.java @@ -101,6 +101,7 @@ import com.raytheon.uf.edex.database.query.DatabaseQuery; * Dec 13, 2013 2555 rjpeter Added processByCriteria and fixed Generics warnings. * Jan 23, 2014 2555 rjpeter Updated processByCriteria to be a row at a time using ScrollableResults. * Apr 23, 2014 2726 rjpeter Updated processByCriteria to throw exceptions back up to caller. + * Feb 23, 2015 4127 dgilling Added bulkSaveOrUpdateAndDelete(). * * * @author bphillip @@ -1143,4 +1144,26 @@ public class CoreDao extends HibernateDaoSupport { return getSessionFactory().getClassMetadata(daoClass); } } + + /** + * Updates/saves a set of records and deletes a set of records in the + * database in a single transaction. + * + * @param updates + * Records to update or add. + * @param deletes + * Records to delete. + */ + public void bulkSaveOrUpdateAndDelete( + final Collection updates, + final Collection deletes) { + txTemplate.execute(new TransactionCallbackWithoutResult() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + HibernateTemplate ht = getHibernateTemplate(); + ht.saveOrUpdateAll(updates); + ht.deleteAll(deletes); + } + }); + } } From cda75a856f2fc78daa9a28931d93fa60f24c3da4 Mon Sep 17 00:00:00 2001 From: Sarah Pontius Date: Wed, 18 Feb 2015 15:00:39 -0700 Subject: [PATCH 25/34] VLab Issue #6517 - TCMWindTool Fix; fixes #6517 Change-Id: I986c425ecc57ab919f2b43ec6b1c4438e789cd86 Former-commit-id: 054762876e074caa93bb5f50d869d47d8a2106ca [formerly 427c442c31ba87f6c20bdfe17abedceb90fb2e7a] Former-commit-id: 8463da45e658027417e3c683cf2e8660593fcba2 --- .../gfe/userPython/procedures/TCMWindTool.py | 1049 +++++++++++------ .../userPython/utilities/DefineMaxWindGUI.py | 225 ++++ 2 files changed, 910 insertions(+), 364 deletions(-) create mode 100644 cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/DefineMaxWindGUI.py diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCMWindTool.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCMWindTool.py index d268b70202..3bc8a64d85 100644 --- a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCMWindTool.py +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCMWindTool.py @@ -1,22 +1,3 @@ -## -# This software was developed and / or modified by Raytheon Company, -# pursuant to Contract DG133W-05-CQ-1067 with the US Government. -# -# U.S. EXPORT CONTROLLED TECHNICAL DATA -# This software product contains export-restricted data whose -# export/transfer/disclosure is restricted by U.S. law. Dissemination -# to non-U.S. persons whether in the United States or abroad requires -# an export license or other authorization. -# -# Contractor Name: Raytheon Company -# Contractor Address: 6825 Pine Street, Suite 340 -# Mail Stop B8 -# Omaha, NE 68106 -# 402.291.0100 -# -# See the AWIPS II Master Rights File ("Master Rights File.pdf") for -# further licensing information. -## # ---------------------------------------------------------------------------- # This software is in the public domain, furnished "as is", without technical # support, and with no warranty, express or implied, as to its usefulness for @@ -24,19 +5,29 @@ # # TCMWindTool # -# Version 2.4 July 18, 2006 # Version 2.7.1 2 Sept 2010 Modified to Fix RCL error # Version 2.7.2 01 Feb 2011 Fixed Pie Slice Algorithm/Added Backgroun Options +# Version Last 14 Apr 2014 Added User-editable max winds +# Modified On 22 May 2014 Introduced option for handling asymetry +# in inner core (RMW), option to use 85th wind radii reduction based on +# 2009 paper by DeMaria or the NCST Bias Correction scheme outside radius +# of MaxWind, corrected problem with ring of lower wind value introduced +# at times in the transition between 34 knots wind radii and background +# field, and introduced option to use preliminary TCM message being +# pushed to all offices text databases beginning with 2014 season. +# +# Modified: 1 Jun 2014 to fix bugs with Lat grids and add option +# to use WindReductionFactor grids for Mid Atlantic offices. +# Modified: 6 June 2014 to fix bugs with large reduction factors over land. +# Modified: 9 June 2014 to fix GUI option to run or not over Selected Time Range. +# +# Last Modified: 2 July to fix decoding of PRE TCM files +# Whatever options are needed should be carefully coordinated among +# offices. # # Author: Tom LeFebvre # Contributor: Pablo Santos # ---------------------------------------------------------------------------- -# SOFTWARE HISTORY -# Date Ticket# Engineer Description -# ------------ ---------- ----------- -------------------------- -# 3/6/2013 15658 ryu Merge in change from AWIPS I DR 21414, which fixed -# makeMaxWindGrid() for when center is outside domain. -# Mar 13, 2013 1793 bsteffen Performance improvements for TCMWindTool # The MenuItems list defines the GFE menu item(s) under which the # Procedure is to appear. @@ -47,29 +38,40 @@ VariableList = [("Product to\ndecode:", [], "check", ["preTCM","WRKTCM","TCMAT1", "TCMAT2", "TCMAT3", "TCMAT4", "TCMAT5", "TCMEP1", "TCMEP2", "TCMEP3", "TCMEP4", "TCMEP5"]), ("Product to\n decode:", [], "check", - ["TCMCP1", "TCMCP2", "TCMCP3", "TCMCP4", "TCMCP5", - "TCPWP1", "TCPWP2", "TCPWP3", "TCPWP4", "TCPWP5"]), - ("Background\nModel:", "Fcst", "radio", ["GFS40", "NAM12", "ECMWFHiRes", "Fcst"]), - ("Number of Pie Slices?", "4", "radio", ["4", "8", "12", "16"]), + ["PREAT1", "PREAT2", "PREAT3", "PREAT4", "PREAT5", + "PREEP1", "PREEP2", "PREEP3", "PREEP4", "PREEP5"]), + ("Background\nModel:", "Fcst", "radio", ["GFS0p5degGbl", "UKMET", "ECMWFHiRes", "Fcst"]), + ("Number of Pie Slices?", "16", "radio", ["4", "8", "12", "16", "24"]), ("Eye Diameter:", 0, "scale", [0, 100], 1), ("34 knot radius at 3 days (NM):", 100, "scale", [0, 1000], 10), ("34 knot radius at 4 days (NM):", 100, "scale", [0, 1000], 10), ("34 knot radius at 5 days (NM):", 100, "scale", [0, 1000], 10), - ("Decrease Wind over Land by (%):", 10, "scale", [-20, 50], 1), - ("Make Grids over Selected Time Only:", "No", "radio", ["Yes", "No"]), - ("MaxWind Swath for TCWindThreat?", "No", "radio", ["Yes", "No"]), + ("Decrease Wind over Land by (%):", 15, "scale", [-20, 50], 1), + ("Make Grids over \nSelected Time Only:", "No", "radio", ["Yes", "No"]), + ("MaxWind Swath for \nTCWindThreat?", "No", "radio", ["Yes", "No"]), + ("Define Asymmetrical \nMax Winds?", "No", "radio", ["Yes", "No"]), + ("Reduce Radii by 15% or \n NC State Bias Correction", "Reduce by 15%", + "radio", ["Reduce by 15%", "NC State Bias Correction"]), + ("Constant Land\nReduction (Slider Bar)\nor Wind Reduction\nFactor Grid?", + "Constant", "radio", ["Constant", "Grid"]), ] -import TimeRange -import AbsTime +try: # See if this is the AWIPS I environment + from Numeric import * + import AFPS + AWIPS_ENVIRON = "AWIPS1" +except: # Must be the AWIPS II environment + from numpy import * + import AbsTime + import TimeRange + AWIPS_ENVIRON = "AWIPS2" import SmartScript +import DefineMaxWindGUI +import MetLib -import string, time -from numpy import * - - -## For available commands, see SmartScript +import popen2, string, time, os, cPickle +import Exceptions, types, copy class TCMDecoder: def __init__(self): @@ -95,26 +97,26 @@ class TCMDecoder: "RADIUS OF 050 KT WINDS" : self.decodeJTWCRadii, "RADIUS OF 064 KT WINDS" : self.decodeJTWCRadii, "RADIUS OF 100 KT WINDS" : self.decodeJTWCRadii, - " ---" : self.endJTWCWindForecast, + " ---" : self.endJTWCWindForecast, "REMARKS:" : self.stopDecodingJTWC, } self.fcstList = [] # a place to store all of the forecasts self.text = [] # the text product - + self.currentFcst = {} # the current forecast we are docoding self.baseProductTime = 0 self.foundEyeDiameter = 0 - - self.AltFileName = "" + self.AltFileName = "" + def calcEyeDiameter(self, center, maxWind): lat = center[0] # latitude in degrees - maxWindC = maxWind / 1.944 # convert to meters per second - rmw = 46.29 * exp(-0.0153 * maxWindC + 0.0166 * lat) + maxWind = maxWind / 1.944 # convert to meters per second + rmw = 46.29 * exp(-0.0153 * maxWind + 0.0166 * lat) # convert to diameter and convert from km to nm ed = rmw * 2.0 / 1.852 @@ -122,7 +124,7 @@ class TCMDecoder: def stripText(self): endStr = chr(13) + chr(13) + chr(10) - for i in xrange(len(self.text)): + for i in range(len(self.text)): self.text[i] = string.replace(self.text[i], endStr, "") return @@ -131,15 +133,15 @@ class TCMDecoder: def getBaseProductTime(self): return self.baseProductTime - + def getAltInfoFileName(self): return self.AltFileName - + def currentLine(self): return self.text[self.pos] def nextLine(self): - self.pos += 1 + self.pos = self.pos + 1 if self.pos < len(self.text): return self.text[self.pos] else: @@ -152,7 +154,7 @@ class TCMDecoder: try: return monthList.index(monthStr) + 1 except ValueError: - return 0 + return 0 def convertBaseTime(self, timeStr): # timeStr format: "HHMM UTC DAY MON DD YYYY" @@ -197,10 +199,10 @@ class TCMDecoder: month = tupleTime[1] # see if we crossed over to a new month if tupleTime[2] > day: - month += 1 + month = month + 1 if month > 12: month = 1 - year += 1 + year = year + 1 newTuple = (year, month, day, hour, minute, tupleTime[5], tupleTime[6], tupleTime[7], tupleTime[8]) @@ -220,17 +222,17 @@ class TCMDecoder: for c in hhmm: if not c in string.digits: return - + baseTime = self.convertBaseTime(timeStr) self.baseProductTime = baseTime return - + def decodeAltFileName(self): nameStr = self.currentLine() parts = string.split(nameStr) self.AltFileName = parts[-1] # grab the last string token - #print "I AM HERE AND AltFileName is: ", self.AltFileName + return def decodeCenterLocation(self): @@ -238,7 +240,7 @@ class TCMDecoder: # check for the repeat center....don't want this one if string.find(locStr, "REPEAT") >= 0: return - + keyWord = "NEAR" pos = string.find(locStr, keyWord) if pos > -1: # found it @@ -265,9 +267,9 @@ class TCMDecoder: pos = string.find(presStr, keyWord) if pos > -1: # found it presStr = presStr[pos + len(keyWord):] - + return - + def decodeMaxSustainedWinds(self): keyWord = "MAX SUSTAINED WINDS" windStr = self.currentLine() @@ -275,7 +277,7 @@ class TCMDecoder: if pos > -1: # found it windList = [] tokenList = string.split(windStr) - for i in xrange(len(tokenList)): + for i in range(len(tokenList)): if string.find(tokenList[i], "KT") >= 0: windList.append(float(tokenList[i - 1])) @@ -300,7 +302,7 @@ class TCMDecoder: self.currentFcst['maxWind']) else: # otherwise use what's been defined or read from the text self.currentFcst['eyeDiameter'] = self.defaultEyeDiameter - + return def decodeMaxWind(self): @@ -356,17 +358,17 @@ class TCMDecoder: # store the radii info self.currentFcst['radii'][radiiWindValue] = radiusList - return + return def decodeWindForecast(self): # if we're decoding a new forecast, save the old one first if self.currentFcst != {}: self.fcstList.append(self.currentFcst) self.currentFcst = {} # reset - + str = self.currentLine() str = string.replace(str, '...', ' ') # remove ... - + tokenList = string.split(str) # decode the validTime validTime = self.convert_ddhhmm(tokenList[2], self.baseProductTime) @@ -397,7 +399,7 @@ class TCMDecoder: self.currentFcst['eyeDiameter'] = diameter - # Since we found it in the product, set the default diameter + # Since we found it in the procuct, set the default diameter self.defaultEyeDiameter = diameter self.foundEyeDiameter = 1 # mark that we found it return @@ -409,7 +411,7 @@ class TCMDecoder: self.defaultEyeDiameter = eyeDiameter self.stripText() - + try: while self.pos < len(TCMProduct): line = self.currentLine() @@ -418,16 +420,16 @@ class TCMDecoder: self.keyWordDict[k]() break self.pos = self.pos + 1 - + # store the last forecast in the list of forecasts if self.currentFcst != {}: self.fcstList.append(self.currentFcst) self.currentFcst = {} # reset except: - # Some problem occurred during the decoding process so return an empty fcst + # Some problem occured during the decoding process so return an empty fcst self.baseProductTime = 0 self.fcstList = {} # reset - + return def decodeLatLonToken(self, latLonStr): @@ -456,7 +458,7 @@ class TCMDecoder: self.baseProductTime = int(self.baseProductTime / 3600) * 3600 return None - + def decodeJTWCTimeCenter(self): line = self.nextLine() tokenList = string.split(line) @@ -468,7 +470,7 @@ class TCMDecoder: lat = self.decodeLatLonToken(latStr) lon = self.decodeLatLonToken(lonStr) if lon > 0: - lon -= 360.0 + lon = lon - 360.0 productTime = self.convert_ddhhmm(dateTimeStr, self.baseProductTime) # make a new fcst object to store the analysis @@ -477,7 +479,7 @@ class TCMDecoder: self.currentFcst['centerLocation'] = (lat, lon) self.currentFcst['radii'] = {} self.currentFcst['eyeDiameter'] = self.defaultEyeDiameter - + def decodeJTWCWindForecast(self): line = self.nextLine() @@ -497,7 +499,7 @@ class TCMDecoder: lat = self.decodeLatLonToken(latStr) lon = self.decodeLatLonToken(lonStr) if lon > 0: - lon -= 360.0 + lon = lon - 360.0 # make a new currentFcst and store the info self.currentFcst = {} @@ -505,7 +507,7 @@ class TCMDecoder: self.currentFcst['centerLocation'] = (lat, lon) self.currentFcst['radii'] = {} self.currentFcst['eyeDiameter'] = self.defaultEyeDiameter - return + return def decodeJTWCRadii(self): line = self.currentLine() @@ -528,10 +530,10 @@ class TCMDecoder: radius = float(tokenList[6]) radList = [radius] else: # no RADIUS found so maybe a QUADRANT line - if string.find(line, "QUADRANT") >= 0: + if string.find(line, "QUADRANT") >= 0: radius = float(tokenList[0]) radList.append(radius) - + line = self.nextLine() # save the last radii info @@ -542,22 +544,22 @@ class TCMDecoder: self.fcstList.append(self.currentFcst) self.currentFcst = {} - return + return def endJTWCWindForecast(self): - + if self.currentFcst != {}: self.fcstList.append(self.currentFcst) self.currentFcst = {} - return - + return + def stopDecodingJTWC(self): line = "ZZZZZ" while line != "": line = self.nextLine() return - + # end class TCMDecoder # begin class CircleEA @@ -570,18 +572,18 @@ class CircleEA(SmartScript.SmartScript): self.xDist = (lonGrid - center[1]) * 111.1 * cosLatGrid self.yDist = (latGrid - center[0]) * 111.1 self.distGrid = sqrt(pow(self.xDist, 2)+ pow(self.yDist, 2)) - + self.tanGrid = arctan2(-self.xDist, -self.yDist) # mask off all but the specified quadrant. self.quadList = [] - for quad in xrange(1, slices + 1): + for quad in range(1, slices + 1): minValue = -pi + (quad - 1) * 2 * pi / slices maxValue = -pi + quad * 2 * pi / slices quadrant = logical_and(greater_equal(self.tanGrid, minValue), less(self.tanGrid, maxValue)) self.quadList.append(quadrant) - + return # Return an edit area for just one quadrant. @@ -599,13 +601,62 @@ class CircleEA(SmartScript.SmartScript): def getXYDistGrids(self): return self.xDist, self.yDist - + # end class CircleEA ------------------------------------------------------- class Procedure (SmartScript.SmartScript): def __init__(self, dbss): SmartScript.SmartScript.__init__(self, dbss) + self._dbss = dbss + + # Make a timeRange based on the start and end int times + def makeTimeRange(self, start=0, end=0): + + if AWIPS_ENVIRON == "AWIPS1": + + if start == 0 and end == 0: + return AFPS.TimeRange.allTimes() + + startTime = AFPS.AbsTime(start) + endTime = AFPS.AbsTime(end) + + tr = AFPS.TimeRange(startTime, endTime) + + elif AWIPS_ENVIRON == "AWIPS2": + if start == 0 and end == 0: + startTime = AbsTime.AbsTime(start) + endTime = AbsTime.maxFutureTime() + else: + startTime = AbsTime.AbsTime(start) + endTime = AbsTime.AbsTime(end) + + tr = TimeRange.TimeRange(startTime, endTime) + else: + self.statusBarMsg("Unknown AWIPS version", "U") + tr = None + + return tr + + def getParmTimeConstraints(self, weName, dbName): + + parm = self.getParm(dbName, weName, "SFC") + + if AWIPS_ENVIRON == "AWIPS1": + parmStart = parm.timeConstraints().startTime() + parmDuration = parm.timeConstraints().duration() + parmRepeat = parm.timeConstraints().repeatInterval() + + elif AWIPS_ENVIRON == "AWIPS2": + parmStart = parm.getGridInfo().getTimeConstraints().getStartTime() + parmDuration = parm.getGridInfo().getTimeConstraints().getDuration() + parmRepeat = parm.getGridInfo().getTimeConstraints().getRepeatInterval() + else: + self.statusBarMsg("Unknown AWIPS version", "U") + return None, None, None + + return parmStart, parmDuration, parmRepeat + # Use this method if you have no luck getting products # directly from the text database @@ -620,10 +671,30 @@ class Procedure (SmartScript.SmartScript): f.close() return textList - def printFcst(self, f, baseTime): + # Retrieves a text product from the text database + def getTextProductFromDB(self, productID): + + cmd = "textdb -r " + productID + + # if your path does not include FXA_HOME/bin, + # this line may work instead of the above line. +# cmd = "/awips/fxa/bin/textdb -r " + productID + + (stdout, stdin, stderr) = popen2.popen3(cmd) + + textList = [] + line = stdout.readline() + textList.append(line) + while line != "": + line = stdout.readline() + textList.append(line) + return textList + + def printFcst(self, f, baseTime=None): print "==============================================================" print "Time:", time.asctime(time.gmtime(f['validTime'])), - print "LeadTime:", (f['validTime'] - baseTime) / 3600 + 3 + if baseTime is not None: + print "LeadTime:", (f['validTime'] - baseTime) / 3600 + 3 print "Center:", f['centerLocation'] print "Eye:", f['eyeDiameter'] if f.has_key('maxWind'): @@ -635,15 +706,15 @@ class Procedure (SmartScript.SmartScript): print r, "kts:", f['radii'][r] def getWEInventory(self, modelName, WEName, level): - yesterday = self._gmtime() - (2 * 24 * 3600) # two days ago - later = self._gmtime() + 10 * 24 * 3600 # 10 days from now - allTimes = TimeRange.TimeRange(yesterday, later) - parm = self.getParm(modelName, WEName, level); - inv = parm.getGridInventory(allTimes.toJavaObj()) + allTimes = self.makeTimeRange(0, 0) + gridInfo = self.getGridInfo(modelName, WEName, level, allTimes) trList = [] - for gd in inv: - tr = TimeRange.TimeRange(gd.getGridTime()) + for g in gridInfo: + start = g.gridTime().startTime().unixTime() + end = g.gridTime().endTime().unixTime() + tr = self.makeTimeRange(start, end) trList.append(tr) + return trList def timeRangeSort(self, a, b): @@ -651,15 +722,15 @@ class Procedure (SmartScript.SmartScript): return -1 else: return 1 - + # returns a wind grid from the specified model most closely matched in # time def getClosestWindGrid(self, modelName, bgDict, timeTarget): topo = self.getTopo() calmGrid = self.makeWindGrid(0.0, 0.0, topo.shape) - + if len(bgDict.keys()) == 0: - print "No background grids available...Using calm grid." +# print "No background grids available...Using calm grid." return calmGrid minDiff = 3600 * 24 * 365 # just a large number @@ -669,12 +740,12 @@ class Procedure (SmartScript.SmartScript): # sort the keys by time so we get consistent behavior bgKeys = bgDict.keys() bgKeys.sort(self.timeRangeSort) - + targetTR = self.makeTimeRange(timeTarget, timeTarget + 3600) # figure out which grid is closest in time for invTR in bgKeys: # if we have an exact match, we're done - if invTR.contains(AbsTime.AbsTime(timeTarget)): + if invTR.overlaps(targetTR): tr = invTR # set the tr minDiff = 0 break @@ -711,7 +782,7 @@ class Procedure (SmartScript.SmartScript): convV = cycU u = cycU * cycWt + convU * convWt v = cycV * cycWt + convV * convWt - mag, dir = self.UVToMagDir(u, v) + mag, dir = self.UVToMagDir(u, v) return dir @@ -739,7 +810,7 @@ class Procedure (SmartScript.SmartScript): while len(f2Radii[r]) < 4: f2Radii[r].append(0) - for i in xrange(4): + for i in range(4): r1 = f1Radii[r][i] r2 = f2Radii[r][i] radius = r1 + (r2 - r1) * (newTime - t1) / (t2 - t1) @@ -748,11 +819,12 @@ class Procedure (SmartScript.SmartScript): return newRadii - # interpolates the wind forecasts inbetween the two specified forecasts. + # interpolate the wind forecasts inbetween the two specified forecasts. # interval is assumed to be specified in hours. # returns a new list of forecasts with f1 at the front of the list # and f2 not present at all in the list. def interpolateWindFcst(self, f1, f2, interval): + intSecs = 3600 * interval t1 = f1['validTime'] t2 = f2['validTime'] @@ -776,13 +848,22 @@ class Procedure (SmartScript.SmartScript): dMaxWind = (f2MaxWind - f1MaxWind) / timeSlots f1Radii = f1['radii'] f2Radii = f2['radii'] + + if f1.has_key("editedMaxWinds"): + emw1 = array(f1["editedMaxWinds"]) + emw2 = array(f2["editedMaxWinds"]) + demw = (emw2 - emw1) / timeSlots + fcstList = [f1] # include the first fcst in the list - for i in xrange(1, timeSlots): + for i in range(1, timeSlots): newTime = t1 + (i * intSecs) newLat = f1Lat + (i * dLat) newLon = f1Lon + (i * dLon) newEye = f1Eye + (i * dEye) newMaxWind = f1MaxWind + (i * dMaxWind) + if f1.has_key("editedMaxWinds"): + newEMW = emw1 + (i * demw) + newRadii = self.interpRadii(t1, t2, newTime, f1Radii, f2Radii) f = {} f['centerLocation'] = (newLat, newLon) @@ -790,28 +871,33 @@ class Procedure (SmartScript.SmartScript): f['validTime'] = newTime f['maxWind'] = newMaxWind f['radii'] = newRadii + if f1.has_key("editedMaxWinds"): + f['editedMaxWinds'] = list(newEMW) fcstList.append(f) return fcstList def calcRadiusList(self, maxWind, rmw, rad34, newRadii): - for i in xrange(len(newRadii)): + for i in range(len(newRadii)): # linearly interpolate newRadii[i] = rmw + ((rmw - rad34) / (maxWind - 34.0)) / (64.0 - maxWind) if newRadii[i] < 0: newRadii[i] = 0 return newRadii + # This method fills in radii/wind values one way for the 36-72 hour period # and another way for the 72-120 hour period. The concept is to add more # data values to the wind field so that the wind grids look more realistic. def extrapolateRadii(self, fcstList, baseDecodedTime, radiiFactor): - for i in xrange(1, len(fcstList)): + for i in range(1, len(fcstList)): fcst = fcstList[i] prevFcst = fcstList[i-1] # calc the lead time in hours leadTime = (fcst['validTime'] - baseDecodedTime) / 3600 + 3 + + extRadius = self.getOutlookRadius(leadTime) zeroRadius = extRadius * radiiFactor @@ -836,23 +922,24 @@ class Procedure (SmartScript.SmartScript): prev64 = prevFcst['radii'][64] fcst50 = fcst['radii'][50] newRadii = [0, 0, 0, 0] - for i in xrange(len(prev50)): + for i in range(len(prev50)): if prev50[i] == 0: continue newRadii[i] = fcst50[i] / prev50[i] * prev64[i] - fcst['radii'][64] = newRadii + if not fcst['radii'].has_key(64): + fcst['radii'][64] = newRadii # add in a 5 knot radius for better blending fcst['radii'][5.0] = [zeroRadius, zeroRadius, zeroRadius, zeroRadius] - + elif leadTime > 72: # different algorithm for beyond 72 hours # if there are radii already defined, don't extrapolate new radii if fcst.has_key("radii"): if len(fcst["radii"]) > 0: continue - + # Stuff radii into the rDict to make a cyclone maxWind = 0 if fcst.has_key("maxWind"): @@ -860,24 +947,23 @@ class Procedure (SmartScript.SmartScript): rDict = {} - # add the radii for maxWind at the rmw + # add the radii for maxWind at the rmw if maxWind > 0: # calculate an rmw lat = fcst["centerLocation"][0] # latitude in degrees rmw = 46.29 * exp(-0.0153 * (maxWind / 1.944) + 0.0166 * lat) - rmw /= 1.852 # convert to nautical miles + rmw = rmw / 1.852 # convert to nautical miles for ws in [64.0, 50.0]: newRadii = [0, 0, 0, 0] if ws < maxWind: newRadii = self.calcRadiusList(maxWind, rmw, extRadius, newRadii) rDict[ws] = newRadii - + rDict[34.0] = [extRadius, extRadius, extRadius, extRadius] rDict[5.0] = [zeroRadius, zeroRadius, zeroRadius, zeroRadius] fcst['radii'] = rDict -# print "From extrapolateRadii added rDict:", rDict - + return fcstList # Smooths the specified grid by the specified factor @@ -888,13 +974,19 @@ class Procedure (SmartScript.SmartScript): # factors of less than 3 are useless or dangerous if factor < 3: return grid + + # Specifying the grid type depends on the environment + + typecode = float64 + st = time.time() half = int(factor)/ 2 - sg = zeros(grid.shape,float64) - count = zeros(grid.shape,float64) - gridOfOnes = ones(grid.shape,float64) - for y in xrange(-half, half + 1): - for x in xrange(-half, half + 1): + sg = zeros(grid.shape, typecode) + count = zeros(grid.shape, typecode) + gridOfOnes = ones(grid.shape, typecode) + + for y in range(-half, half + 1): + for x in range(-half, half + 1): if y < 0: yTargetSlice = slice(-y, None, None) ySrcSlice = slice(0, y, None) @@ -916,46 +1008,90 @@ class Procedure (SmartScript.SmartScript): target = [yTargetSlice, xTargetSlice] src = [ySrcSlice, xSrcSlice] - sg[target] += grid[src] - count[target] += gridOfOnes[src] + sg[target] = sg[target] + grid[src] + count[target] = count[target] + gridOfOnes[src] return sg / count # Smooths the direction grid without regard to the magnitude - def smoothDirectionGrid(self, dir, factor): - mag = ones(dir.shape, float) # 1.0 everywhere - u, v = self.MagDirToUV(mag, dir) + def smoothDirectionGrid(self, dirGrid, factor): + mag = ones(dirGrid.shape, float32) # 1.0 everywhere + u, v = self.MagDirToUV(mag, dirGrid) u = self.smoothGrid(u, factor) v = self.smoothGrid(v, factor) - mag, dir = self.UVToMagDir(u, v) - return dir - + mag, dirGrid = self.UVToMagDir(u, v) + return dirGrid + def makeWindGrid(self, mag, direct, gridShape): - mag = ones(gridShape, float) * mag - direct = ones(gridShape, float) * direct + mag = ones(gridShape, float32) * mag + direct = ones(gridShape, float32) * direct return mag, direct - def decreaseWindOverLand(self, grid, fraction, Topo): - mask = greater(Topo, 0.0) - + def decreaseWindOverLand(self, grid, fraction, Topo, timeRange): + + if self.lessOverLandGrid == "Grid": + + windFactorGrid = self.getWindReductionFactorGrid("Fcst", timeRange) + if windFactorGrid is not None: + # Restrict reduction to the cyclone winds defined by the TCM + grid = where(self._cycloneMask, grid * (1 - windFactorGrid), grid) + return grid + else: + # If no grid was found just return the standard reduction + self.statusBarMsg("Wind Reduction Factor grid not found. Using standard reduction." , "S") + # If area over which you desire to apply land correction you prefer be # based on Edit Are instead of areas with Topo greater than zero then # uncomment the next two lines and specify Edit Area to use. - #editArea = self.getEditArea("LAND_EDIT_AREA_NAME_HERE") + #editArea = self.getEditArea("LAND_EDIT_ARE_NAME_HERE") #mask = self.encodeEditArea(editArea) - gridC = where(mask, grid * fraction, grid) - return gridC + # Restrict reduction to the cyclone winds defined by the TCM + mask = logical_and(greater(Topo, 0.0), self._cycloneMask) + + grid = where(mask, grid * fraction, grid) + + return grid + + # fetches and returns all of the wind reduction factor grids in Fcst DB. + def getWindReductionFactorGrid(self, modelName, timeRange): + try: + inv = self.getWEInventory(modelName, "WindReductionFactor", "SFC") + for tr in inv: + if tr.overlaps(timeRange): + WindRedGrid = self.getGrids(modelName, "WindReductionFactor", "SFC", + timeRange, mode="First") + return WindRedGrid + # If no overlapping grids, return None + return None + except: + return None + def getTimeConstraintDuration(self, element): - return self.getParm("Fcst", element, "SFC").getGridInfo()\ - .getTimeConstraints().getDuration() + + parmStart, parmDuration, parmRepeat = self.getParmTimeConstraints(element, "Fcst") + return parmDuration + def getParmMinMaxLimits(self, modelName, weName): + + parm = self.getParm(modelName, weName, "SFC") + + if AWIPS_ENVIRON == "AWIPS1": + return parm.minLimit(), parm.maxLimit() + elif AWIPS_ENVIRON == "AWIPS2": + return parm.getGridInfo().getMinValue(), parm.getGridInfo().getMaxValue() + else: + self.statusBarMsg("Unknown AWIPS version", "U") + return None, None + + return + # returns the maximum allowable wind speed based on NWS directives def getMaxAllowableWind(self, maxWind): - parm = self.getParm("Fcst", "Wind", "SFC") - maxAllowable = parm.getGridInfo().getMaxValue() - return min(maxWind, maxAllowable) + minAllowable, maxAllowable = self.getParmMinMaxLimits("Fcst", "Wind") + return min(maxWind, maxAllowable) + # returns an interpolated radius based on input radii def getOutlookRadius(self, leadTime): leadTimeList = [72, 96, 120] @@ -963,8 +1099,8 @@ class Procedure (SmartScript.SmartScript): if leadTime < leadTimeList[0]: return radiusList[0] - - for i in xrange(1, len(leadTimeList)): + + for i in range(1, len(leadTimeList)): if leadTime < leadTimeList[i]: dt = leadTimeList[i] - leadTimeList[i - 1] dr = radiusList[i] - radiusList[i - 1] @@ -975,98 +1111,54 @@ class Procedure (SmartScript.SmartScript): # Blends the specified grid together def blendGrids(self, windGrid, bgGrid): - # make a mask around the edge - windMag = windGrid[0] - backMag = bgGrid[0] - mag = windMag.copy() + # Combine the two grids using the windGrid for the cyclone and the + # background grid everywhere else. - # if we have a calm cyclone, return the background grid - windMask = greater(mag, 1.0) - if sum(sum(windMask)) == 0: - return bgGrid + windMag, windDir = windGrid + bgMag, bgDir = bgGrid - # now check the background grid - bgMask = greater(backMag, 1.0) - if sum(sum(bgMask)) == 0: - return windGrid + mask = greater_equal(windMag, 34.0) - # make a weightingGrid - upper = 33.9 # start blending at this mag - # stop blending at the average background mag - lower = sum(sum(backMag)) / sum(sum(ones(backMag.shape))) - if lower >= upper: - print "Problem calculating lower and upper ring thresholds." - print "lower = ", lower, "upper = ", upper - return bgGrid + # No background winds inside any defined wind radii + # Add in the point inside the defined wind radii + mask = logical_or(mask, self._cycloneMask) - # calculate the average value over the area where blending will occur - ringMask = logical_and(less_equal(mag, upper), greater_equal(mag, lower)) + magGrid = where(mask, windMag, bgMag) + dirGrid = where(mask, windDir, bgDir) - ringMaskSum = sum(sum(ringMask)) - if ringMaskSum == 0: - print "Problem calculating ringMask. No blending for this grid." - windMag = self.smoothGrid(windMag, 9) - ringMask = logical_and(less_equal(mag, upper), greater_equal(mag, lower)) - windMask = greater(windMag, 5.0) - magGrid = where(windMask, windMag, backMag) - dirGrid = where(windMask, windGrid[1], bgGrid[1]) - return (magGrid, dirGrid) - - avgGrid = where(ringMask, backMag, 0.0) - lower = sum(sum(avgGrid)) / sum(sum(ringMask)) - - # a nearly calm grid means no blending required so return the cyclone - if lower < 1.0: - return windGrid - - wtGrid = where(greater(mag, upper), 1.0, 0.0) - ringMask = logical_and(less(mag, upper), greater(mag, lower)) - wtGrid = where(ringMask, (mag - lower) / (upper - lower), wtGrid) - wtGrid[less(mag, lower)] = 0.0 - wtGrid = self.smoothGrid(wtGrid, 5) - - # calculate the new mag grid - mag *= wtGrid - mag += backMag * (1 - wtGrid) - - # calculate direction grid - onesGrid = ones(mag.shape) - gridU, gridV = self.MagDirToUV(onesGrid, windGrid[1]) - bgU, bgV = self.MagDirToUV(onesGrid, bgGrid[1]) - gridU *= wtGrid - gridU += bgU * (1 - wtGrid) - gridV *= wtGrid - gridV += bgV * (1 - wtGrid) - - # get the dirGrid and toss out the magnitude - magGrid, dirGrid = self.UVToMagDir(gridU, gridV) - - return mag, dirGrid + return magGrid, dirGrid def getLatLonGrids(self): # Try to get them from the fcst database to save time - startTime = AbsTime.current() - 86400 - endTime = AbsTime.current() + 86400 # 1 day - timeRange = TimeRange.TimeRange(startTime, endTime) - latGrid = self.getGrids("Fcst", "latGrid", "SFC", timeRange, - mode = "First", noDataError = 0) - lonGrid = self.getGrids("Fcst", "lonGrid", "SFC", timeRange, - mode = "First", noDataError = 0) - if latGrid != None and lonGrid != None: - return latGrid, lonGrid + try: + trList = self.getWEInventory("Fcst", "latGrid", "SFC") + except: + trList= [] - # make the latGird and lonGrid - latGrid, lonGrid = SmartScript.SmartScript.getLatLonGrids(self) - - # Temporarliy save them in the forecast database - startTime = AbsTime.current() - endTime = AbsTime.current() + 86400 * 7 # 7 days - timeRange = TimeRange.TimeRange(startTime, endTime) + if len(trList) > 0: + timeRange = trList[0] + latGrid = self.getGrids("Fcst", "latGrid", "SFC", timeRange, + mode = "First", noDataError = 0) + lonGrid = self.getGrids("Fcst", "lonGrid", "SFC", timeRange, + mode = "First", noDataError = 0) + if latGrid != None and lonGrid != None: + return latGrid, lonGrid + + # make the lat and lon grids + gridLoc = self.getGridLoc() + + latGrid, lonGrid = MetLib.getLatLonGrids(gridLoc) + + start = int(time.time() / (24 * 3600)) * 24 * 3600 + end = start + (24 * 3600) + timeRange = self.makeTimeRange(start, end) + + # Temporarily save them in the forecast database self.createGrid("Fcst", "latGrid", "SCALAR", latGrid, timeRange, descriptiveName=None, timeConstraints=None, - precision=1, minAllowedValue=0.0, + precision=1, minAllowedValue=-90.0, maxAllowedValue=90.0) - + self.createGrid("Fcst", "lonGrid", "SCALAR", lonGrid, timeRange, descriptiveName=None, timeConstraints=None, precision=1, minAllowedValue=-360.0, @@ -1088,11 +1180,11 @@ class Procedure (SmartScript.SmartScript): interpFactor = pieSlices / len(rList) newList = [] - for i in xrange(-1, len(rList) -1): + for i in range(-1, len(rList) -1): minVal = rList[i] maxVal = rList[i + 1] dVal = (maxVal - minVal) / interpFactor - for f in xrange(interpFactor): + for f in range(interpFactor): radius = minVal + dVal * f # make sure we never exceed the forecast radius ## if radius > minVal: @@ -1103,13 +1195,16 @@ class Procedure (SmartScript.SmartScript): # the list so that it starts at North to conform to convention shift = int(pieSlices / 4) shiftedList = newList[shift:] - shiftedList += newList[:shift] + shiftedList = shiftedList + newList[:shift] newDict[k] = shiftedList return newDict + # fetches and returns all of the wind grids specified by the model # name. Should be called before any new wind grids are created def getBackgroundGrids(self, modelName): + bgDict = {} + siteID = self.getSiteID() if modelName == "Fcst": level = "SFC" @@ -1123,64 +1218,149 @@ class Procedure (SmartScript.SmartScript): level = "FHAG10" elementName = "wind" - inv = self.getWEInventory(modelName, elementName, level) - bgDict = self.getGrids(modelName, elementName, level, inv, - mode="First") + for tr in inv: + bgDict[tr] = self.getGrids(modelName, elementName, level, + tr, mode="First") return bgDict - - def secondsToYYYYMMDDHH(self, baseTime): - gTime = time.gmtime(baseTime) - return time.strftime("%Y%m%d%H", gTime) + def secondsToYYYYMMDDHH(self, baseTime): + # convert the base time to a string + gTime = time.gmtime(baseTime) + yearStr = str(gTime.tm_year) + monthStr = str(gTime.tm_mon) + dayStr = str(gTime.tm_mday) + hourStr = str(gTime.tm_hour) + while len(monthStr) < 2: + monthStr = "0" + monthStr + while len(dayStr) < 2: + dayStr = "0" + dayStr + while len(hourStr) < 2: + hourStr = "0" + hourStr + + baseTimeStr = yearStr + monthStr + dayStr + hourStr + + return baseTimeStr + + # returns the index corresponding to the specified timeStr and fcstHour def findFcst(self, fcstList, fcstHour): - for i in xrange(len(fcstList)): + for i in range(len(fcstList)): validTime = fcstList[i]["validTime"] leadTime = (validTime - self.baseDecodedTime) / 3600 if fcstHour == leadTime: return i - return None + return None + + # Accepts the number of slices to interpolate and a list of defined + # wind values. Returns a new list of length slices with the specified + # windList interpolated to the new resolution. + def interpWindMax(self, slices, windList): + + maxWindList = [0.0] * slices + + quads = len(windList) + ratio = slices / quads + intOffset = int(ratio / 2) + floatOffset = float(intOffset) / ratio + sliceMap = [] + windPos = [0] * len(windList) + + # Figure out the left and right positions for each new slice + for i in range(slices): + left = int((i - int(ratio/2)) / ratio) + if i % ratio == int(ratio/2): + right = left + windPos[left] = i + else: + right = left + 1 + + if right >= quads: + right = right - quads + + sliceMap.append((left, right)) + + # Do the actual interpolation based on the above positions + interpWindList = [] + for i in range(slices): + left, right = sliceMap[i] + + if left == right: + val = windList[left] + absDist = 1.1111 + elif windPos[left] > windPos[right]: + absDist = slices - abs(windPos[right] - windPos[left]) + else: + absDist = abs(windPos[right] - windPos[left]) + + diff = i - windPos[left] + if diff < 0: + diff = slices + diff + val = windList[left] + diff * ((windList[right] - windList[left]) / absDist) + interpWindList.append(val) + + return interpWindList + + + # Calculate the radius of the maxWind based on teh specified eyeDiameter + def maxWindRadius(self, eyeDiameter=None): + + if eyeDiameter is None: + return 12.5 + + rmw = (eyeDiameter / 2.0) + 8.0 + + return rmw + + def adjustMaxWind(self, outSpeed, inSpeed, outRadius, inRadius, + globalMaxWind, maxWindList, maxWindRadius, quad): + + maxWind = maxWindList[quad] + + # check which speed/radius should be modified + if outSpeed == globalMaxWind: + outSpd = maxWind + outRad = maxWindRadius + inSpd = inSpeed + inRad = inRadius + elif inSpeed == globalMaxWind: + inSpd = maxWind + inRad = maxWindRadius + outSpd = outSpeed + outRad = outRadius + else: + print "ERROR!!! Neither inSpeed or outSpeed is max!!!" + + return outSpd, inSpd, outRad, inRad, maxWind # Makes a Rankine Vortex wind speed grid that decreases exponentially # from the known values at known radii. Inside the Radius of maximum # wind the wind decreases linearly toward the center - def makeRankine(self, f, latGrid, lonGrid, pieSlices, radiiFactor): + def makeRankine(self, f, latGrid, lonGrid, pieSlices, radiiFactor, timeRange): st = time.time() grid = zeros(latGrid.shape) rDict = f['radii'] - -## print "rDict before interpolating Quads:" -## for r in rDict: -## print rDict[r] rDict = self.interpolateQuadrants(rDict, pieSlices) - -## print "rDict after interpolating Quads:" -## for r in rDict: -## print rDict[r] -## -## return (None, None) validTime = f['validTime'] center = f['centerLocation'] maxWind = f['maxWind'] + circleEA = CircleEA(latGrid, lonGrid, center, pieSlices) # make a list that contains the highest non-zero radius speed -# centerWindList = [0, 0, 0, 0] centerWindList = [0] * pieSlices for k in rDict.keys(): - for i in xrange(len(rDict[k])): + for i in range(len(rDict[k])): if rDict[k][i] > 0 and k > centerWindList[i]: centerWindList[i] = k - for k in rDict.keys(): -# if rDict[k] == [0, 0, 0, 0]: - if rDict[k] == [0] * pieSlices: - del rDict[k] + for k in rDict.keys(): + if rDict[k] == [0] * pieSlices: + del rDict[k] # make a list of lowest wind speed found with zero radius # and save the next lowest wind speed for later if rDict.has_key(100.0): @@ -1188,82 +1368,112 @@ class Procedure (SmartScript.SmartScript): else: speedList = [None, 64.0, 50.0, 34.0, 5.0] -# zeroRadList = [999, 999, 999, 999] -# validRadList = [999, 999, 999, 999] zeroRadList = [999] * pieSlices validRadList = [999] * pieSlices - for s in xrange(len(speedList) - 1): + for s in range(len(speedList) - 1): speed = speedList[s] nextSpeed = speedList[s + 1] if not rDict.has_key(speed): -# zeroRadList = [speed, speed, speed, speed] -# validRadList = [nextSpeed, nextSpeed, nextSpeed, nextSpeed] zeroRadList = [speed] * pieSlices validRadList = [nextSpeed] * pieSlices else: - for i in xrange(len(rDict[speed])): + for i in range(len(rDict[speed])): if rDict[speed][i] == 0: zeroRadList[i] = speed validRadList[i] = nextSpeed # get the distance grid and make sure it's never zero anywhere distanceGrid = circleEA.getDistanceGrid() / 1.852 # dist in NM - distanceGrid[equal(distanceGrid, 0)] = 0.01 + distanceGrid[distanceGrid == 0] = 0.01 # make a grid into which we will define the wind speeds - grid = zeros(latGrid.shape, float) + grid = zeros(latGrid.shape, float32) # The cyclone algorithm depends on the forecast lead time fcstLeadTime = (validTime - self.baseDecodedTime) / 3600 + # add the radius for maxWind for interpolation if f.has_key('eyeDiameter'): - maxRad = f['eyeDiameter'] / 2.0 + 8.0 + eyeDiameter = f['eyeDiameter'] else: print "Error --- no eye diameter found." - maxRad = 12.5 # half of default 25 nm eye diameter + eyeDiameter = None -# rDict[maxWind] = [maxRad, maxRad, maxRad, maxRad] + maxWindRadius = self.maxWindRadius(eyeDiameter) + + maxWindList = [] + # add the edited maxWind values, if any + if f.has_key("editedMaxWinds"): + # First interpolate based on pie slices + maxWindList = self.interpWindMax(pieSlices, f["editedMaxWinds"]) + + # Add in the maxWind and radius as a point if not rDict.has_key('maxWind'): - rDict[maxWind] = [maxRad] * pieSlices - - # make sure no wind exceeds the maximum allowable windspeed - # remove any rDict entries that are higher than this - + rDict[maxWind] = [maxWindRadius] * pieSlices # extract the list and sort it wsList = rDict.keys() wsList.sort() - + # insert a dummy wind near the center and append so it's done last -# rDict[1] = [1, 1, 1, 1] rDict[1] = [1] * pieSlices - wsList.append(1.0) + wsList.append(1.0) # insert an artificial 5 knot radius at a distance proportional # to the 34 knot radius for that quadrant -# tenKnotRadiusList = [108.0, 108.0, 108.0, 108.0] # 200 km tenKnotRadiusList = [108.0] * pieSlices + if rDict.has_key(34.0): tenKnotRadList = [] radList34 = rDict[34.0] for r in radList34: tenKnotRadList.append(r * radiiFactor) - - # insert the 10 knot radius at the beginning so is made first + + # insert the 5 knot radius at the beginning so is made first rDict[5.0] = tenKnotRadList wsList.insert(0, 5.0) + insideRMWMask = zeros(latGrid.shape) + self._cycloneMask = zeros(latGrid.shape, bool) # for each rDict record and quadrant, make the grid one piece at a time - for i in xrange(len(wsList) - 1): + for i in range(len(wsList) - 1): + self.lastRadius = [None] * pieSlices if not rDict.has_key(wsList[i]): continue radiusList = rDict[wsList[i]] nextRadiusList = rDict[wsList[i + 1]] - for quad in xrange(len(radiusList)): + + maxRadius = maxWindRadius # temp copy + for quad in range(len(radiusList)): + + maxRadius = maxWindRadius # temp copy + maxWind = f['maxWind'] # reset maxWind as we may fiddle with it + + # fetch the speeds and radii we'll need outSpeed = float(wsList[i]) inSpeed = float(wsList[i + 1]) outRadius = float(radiusList[quad]) inRadius = float(nextRadiusList[quad]) - # reset the speeds if they exceed the max non-zero wind + + # Here's where the speeds and radii are adjusted based + # on the edited values but only if they have been edited + # and only if we're working on the maxWind. + if f.has_key("editedMaxWinds"): + if maxWind == wsList[i] or maxWind == wsList[i+1]: + outSpeed, inSpeed, outRadius, inRadius, maxWind = \ + self.adjustMaxWind(outSpeed, inSpeed, outRadius, + inRadius, maxWind, maxWindList, + maxWindRadius, quad) + + # Some cases require we adjust the maxWindRadius + if outSpeed in [64.0, 50.0, 34.0] and outRadius <= maxWindRadius: + inRadius = outRadius * 0.9 + self.lastRadius[quad] = outRadius + elif inSpeed == 1.0 and self.lastRadius[quad] is not None: + outRadius = self.lastRadius[quad] * 0.9 + #print "Adjusting MaxWindRadius at:", inSpeed, "kts" + self.lastRadius[quad] = None + + # reset the speeds if they exceed the maxWind if fcstLeadTime <= 72 and zeroRadList[quad] is not None: if inSpeed >= zeroRadList[quad]: inSpeed = validRadList[quad] @@ -1286,7 +1496,6 @@ class Procedure (SmartScript.SmartScript): inRadius = 0.1 if outRadius == 0.0: outRadius = 0.1 - # no wind speed can never exceed the maximum allowable wind speed if inSpeed > maxWind: inSpeed = maxWind @@ -1299,32 +1508,48 @@ class Procedure (SmartScript.SmartScript): if inRadius > outRadius: continue + if inSpeed == 0.0 or outSpeed == 0.0: + continue # calculate the exponent so that we exactly fit the next radius denom = log10(inRadius / outRadius) if denom == 0: exponent = 1.0 - else: + else: exponent = (log10(outSpeed) - log10(inSpeed)) / denom - + # make sure the exponent behaves itself if exponent > 10.0: exponent = 10.0 # inside RMW gets a linear slope to largest of max wind forecasts if inRadius <= 1.0: dSdR = (outSpeed - inSpeed) / (outRadius - inRadius) - grid[mask] = inSpeed + (dSdR * distanceGrid[mask]) + grid = where(mask, inSpeed + (dSdR * distanceGrid), grid) + insideRMWMask = logical_or(insideRMWMask, mask) else: # outside RMW - grid[mask] = inSpeed * power((inRadius / distanceGrid[mask]), exponent) -# grid = clip(grid, 0.0, 200.0) + grid = where(mask, inSpeed * power((inRadius / distanceGrid), exponent), + grid) + if outSpeed >= 34.0 and inSpeed >= 34.0: + self._cycloneMask = logical_or(self._cycloneMask, mask) + + # Apply the NC State correction outside the RMW + if self._applyNCSCorrection: + corrGrid = self.makeCorrectionGrid(latGrid, lonGrid, center) +## self.createGrid("Fcst", "NCSCorr", "SCALAR", corrGrid, self._timeRange, +## precision=3, minAllowedValue=-1.0, maxAllowedValue=1.0) + + grid = where(logical_not(insideRMWMask), grid * (1 - corrGrid), grid) + + maxWind = f['maxWind'] # reset again before clipping + dirGrid = self.makeDirectionGrid(latGrid, lonGrid, center[0], center[1]) + # clip values between zero and the maximum allowable wind speed maxWind = self.getMaxAllowableWind(maxWind) grid = clip(grid, 0.0, maxWind) # apply the wind reduction over land fraction = 1.0 - (self.lessOverLand / 100.0) - grid = self.decreaseWindOverLand(grid, fraction, self.elevation) - + grid = self.decreaseWindOverLand(grid, fraction, self.elevation, timeRange) return (grid, dirGrid) def makeMaxWindGrid(self, interpFcstList, interval, latGrid, lonGrid, pieSlices, @@ -1333,22 +1558,19 @@ class Procedure (SmartScript.SmartScript): ## if len(interpFcstList) == 0: ## return - maxWindGrid = zeros_like(self.getTopo()) + maxWindGrid = zeros(self.getTopo().shape) startTime = interpFcstList[0]["validTime"] endTime = startTime + (123 * 3600) # 123 hours later - - start = AbsTime.AbsTime(startTime) - end = AbsTime.AbsTime(endTime) - timeRange = TimeRange.TimeRange(start, end) - + timeRange = self.makeTimeRange(startTime, endTime) + # Used getGrids to calculate the maximum wind grid. # # Fetch the max of the wind grids just generated as this is very fast. maxWindGrid, maxDirGrid = self.getGrids("Fcst", "Wind", "SFC", timeRange, mode="Max") - maxWindGrid = self.smoothGrid(maxWindGrid, 3) + maxWindGrid = self.smoothGrid(maxWindGrid,3) self.createGrid("Fcst", "TCMMaxWindComposite", "SCALAR", maxWindGrid, timeRange, precision=1, minAllowedValue=0.0, maxAllowedValue=200.0) @@ -1358,24 +1580,7 @@ class Procedure (SmartScript.SmartScript): return - def validateCycloneForecast(self, fcstList, baseTime): - print "Validating Forecasts:" -## -## This section is overly restrictive, so it is commented out pending -## better logic that compares the TCM to the RCL forecasts -## -## leadTimeList = [12, 24, 36, 48, 72, 96, 120] -## fcstHourList = [] -## for f in fcstList: -## # calc the leadTime in hours -## leadTime = (f['validTime'] - baseTime) / 3600 + 3 -## fcstHourList.append(int(leadTime)) -## -## # Make sure all of the forecasts are there -## for leadTime in leadTimeList: -## if leadTime not in fcstHourList: -## return False # Now check each forecast to make sure that we have a radius for any # standard wind values less than the maxWind @@ -1394,15 +1599,93 @@ class Procedure (SmartScript.SmartScript): return True + # Returns a dictionary that lists the min and max allowed wind for each hour + def makeWindDict(self, fcstList): + + windDict = {} + + + for f in fcstList: + windValues = f["radii"].keys() + hour = (f["validTime"] - self.baseDecodedTime) / 3600 + maxWind = f["maxWind"] + minWind = 999999.0 + if len(f["radii"].keys()) == 0: + minWind = 0.0 + + # Grab the first (highest) forecast wind speed value + if len(windValues) > 0: + minWind = windValues[0] + else: + minWind = 0.0 + + windDict[hour] = (minWind, maxWind) + + return windDict + + # Pop up a GUI that will maxWind values for each quadrant and time + def launchMaxWindGUI(self, fcstList): + + windDict = self.makeWindDict(fcstList) + if AWIPS_ENVIRON == "AWIPS1": + eaMgr = self.eaMgr() + else: + eaMgr = None + + self._maxWindGUI = DefineMaxWindGUI.DefineMaxWindGUI(self._dbss, eaMgr) + + newMaxWinds = self._maxWindGUI.displayGUI(windDict) + + if newMaxWinds is not None: + + hourList = newMaxWinds.keys() + hourList.sort() + + self._maxWindGUI.cancelCommand() + + return newMaxWinds + + # Make the NCState bais correction grid based on the forecast. + def makeCorrectionGrid(self, latGrid, lonGrid, center): + + + # structure to hold the polynomial coefficients + coeff = [[1.282e-011, -3.067e-008, 2.16e-005, -5.258e-003, 3.794e-001], + [3.768e-011, -4.729e-008, 2.097e-005, -3.904e-003, 2.722e-001], + [4.692e-011, -5.832e-008, 2.565e-005, -4.673e-003, 2.952e-001], + [3.869e-011, -4.486e-008, 1.84e-005, -3.331e-003, 2.738e-001]] + + # make the circle edit area and distance grid + pieSlices = 4 + circleEA = CircleEA(latGrid, lonGrid, center, pieSlices) + + dist = circleEA.getDistanceGrid() # dist in km + + corrGrid = zeros(dist.shape) + + for quad in range(pieSlices): + + ea = circleEA.getQuadrant(quad + 1, 500.0) + grid = coeff[quad][0] * pow(dist, 4) + coeff[quad][1] * pow(dist, 3) + \ + coeff[quad][2] * pow(dist, 2) + coeff[quad][3] * dist + \ + coeff[quad][4] + + corrGrid = where(ea, grid, corrGrid) + + return corrGrid + def execute(self, varDict, timeRange): + + RADII_FACTOR = 4.5 + self.setToolType("numeric") self.toolTimeRange = timeRange # define the default eye diameter for bulletins where they are missing eyeStr = varDict["Eye Diameter:"] self.dialogEyeDiameter = float(eyeStr) - maxwindswath = varDict["MaxWind Swath for TCWindThreat?"] - + maxwindswath = varDict["MaxWind Swath for \nTCWindThreat?"] + Topo = self.getTopo() tcDuration = self.getTimeConstraintDuration("Wind") @@ -1413,16 +1696,16 @@ class Procedure (SmartScript.SmartScript): # get the product ID productList1 = varDict["Product to\ndecode:"] productList2 = varDict["Product to\n decode:"] - productList1C = productList1 + productList2 # concatenate - if len(productList1C) != 1: + productList1 = productList1 + productList2 # concatenate + if len(productList1) != 1: self.statusBarMsg("Please select one TCM bulletin only.", "S") return None - productID = productList1C[0] + productID = productList1[0] # get the ID for this site siteID = self.getSiteID() - + bgModelName = varDict["Background\nModel:"] self.day3Radius = varDict["34 knot radius at 3 days (NM):"] self.day4Radius = varDict["34 knot radius at 4 days (NM):"] @@ -1435,29 +1718,29 @@ class Procedure (SmartScript.SmartScript): # forecast into more radial pieces. Recommended alternative values: # 12, 20, 36, 72. pieSlices = int(varDict["Number of Pie Slices?"]) - print "Number of Pie Slices: ", pieSlices - # pieSlices = 8 # define radii factor - may make this configurable - radiiFactor = 1.5 + # Multiply 3-5 day radius by this factor to get the zero radius. + # Smaller values ramp the cyclone down to zero more quickly. + self.lessOverLand = int(varDict["Decrease Wind over Land by (%):"]) + self.lessOverLandGrid = varDict["Constant Land\nReduction (Slider Bar)\nor Wind Reduction\nFactor Grid?"] self.elevation = Topo - rclDecoder = TCMDecoder() tcmDecoder = TCMDecoder() msg = "" -# # Use this method to fetch a product from the text database + # Fetch the text product if productID == "preTCM": - textProduct = self.getTextProductFromFile("/tmp/Isaac.txt") + textProduct = self.getTextProductFromFile("/tmp/Wilma.txt") decoder = TCMDecoder() decoder.decodeTCMProduct(textProduct, self.dialogEyeDiameter) fcstList = decoder.getFcstList() baseTime = decoder.getBaseProductTime() #elif productID == "WRKTCM": - # textProduct = self.getTextProductFromFile("/data/local/research/TPCWindProb/WRKTCM") + # textProduct = self.getTextProductFromFile("/data/local/research/TPCWindProb/WRKTCM") else: # try fetching the RCL first. rclProductID = "MIARCL" + productID[3:] @@ -1473,8 +1756,12 @@ class Procedure (SmartScript.SmartScript): rclFcstList = rclDecoder.getFcstList() rclBaseTime = rclDecoder.getBaseProductTime() completeFcst = self.validateCycloneForecast(rclFcstList, rclBaseTime) - + + if productID[:3] == "PRE": + productID = "MIA" + productID + tcmTextProduct = self.getTextProductFromDB(productID) + if len(tcmTextProduct) < 5: msg = productID + " could not be retrieved from the text database." self.statusBarMsg(msg, "S") @@ -1484,10 +1771,10 @@ class Procedure (SmartScript.SmartScript): tcmFcstList = tcmDecoder.getFcstList() tcmBaseTime = tcmDecoder.getBaseProductTime() - print "TCM and RCL Base Times are: ", tcmBaseTime, rclBaseTime + #print "TCM and RCL Base Times are: ", tcmBaseTime, rclBaseTime if not completeFcst or rclBaseTime != tcmBaseTime: msg = "Problem decoding " + rclProductID + " Used TCM to make cyclone.\n" - msg += " Used GUI sliders for 3, 4, 5 day forecast." + msg = msg + " Used GUI sliders for 3, 4, 5 day forecast." #self.statusBarMsg(msg, "S") fcstList = tcmFcstList baseTime = tcmBaseTime @@ -1496,51 +1783,58 @@ class Procedure (SmartScript.SmartScript): fcstList = rclFcstList baseTime = rclBaseTime productID = rclProductID - -## # Use this method to fetch a text product from a file -## textProduct = self.getTextProductFromFile("/tmp/Irene.txt") -## -## decoder = TCMDecoder() -## decoder.decodeTCMProduct(textProduct, self.dialogEyeDiameter) -## fcstList = decoder.getFcstList() -## baseTime = decoder.getBaseProductTime() - + print "Decoded:", len(fcstList), " forecasts." # Set the baseDecodedTime - validTime of first entry - 3 hours if len(fcstList) > 0: self.baseDecodedTime = fcstList[0]['validTime'] - 3 * 3600 - - fcstList = self.extrapolateRadii(fcstList, baseTime, radiiFactor) - # See if the decoded fcst is close to the current time. This is needed - # so the tool will work on archived data sets (testMode) - testMode = 0 - if abs(time.time() - self.baseDecodedTime) > 2 * 24 * 3600: # older than 2 days - testMode = 1 + if varDict["Define Asymmetrical \nMax Winds?"] == "Yes": + + newMaxWinds = self.launchMaxWindGUI(fcstList) + for i in range(len(fcstList)): + fcstHour = (fcstList[i]['validTime'] - baseTime) / 3600 + 3 + maxList = newMaxWinds[fcstHour] + fcstList[i]["editedMaxWinds"] = maxList + + fcstList = self.extrapolateRadii(fcstList, baseTime, RADII_FACTOR) + +## # See if the decoded fcst is close to the current time. This is needed +## # so the tool will work on archived data sets (testMode) +## testMode = False +## if abs(time.time() - self.baseDecodedTime) > 2 * 24 * 3600: # older than 2 days +## testMode = True + # restrict grids to the selected time period if option is selected. - restrictAnswer = varDict["Make Grids over Selected Time Only:"] + testMode = False + restrictAnswer = varDict["Make Grids over \nSelected Time Only:"] if restrictAnswer == "Yes": - testMode = 1 + testMode = True # Turn off testMode if the selected timeRange is less than an hour in duration if self.toolTimeRange.duration() < 3600: - testMode = 0 + testMode = False # interpolate the wind forecasts we got from the decoder - print "Interpolating wind forecasts:" selectedStartTime = self.toolTimeRange.startTime().unixTime() selectedEndTime = self.toolTimeRange.endTime().unixTime() - interpFcstList = [] - for i in xrange(len(fcstList) - 1): + for i in range(len(fcstList) - 1): newFcstList = self.interpolateWindFcst(fcstList[i], fcstList[i+1], interval) + # Make sure the fcst is within the selected time range or we're in testMode for f in newFcstList: if (testMode and (f['validTime'] >= selectedStartTime and \ - f['validTime'] < selectedEndTime)) or testMode == 0: + f['validTime'] < selectedEndTime)) or (not testMode): interpFcstList.append(f) + + # append the very last forecast on to the end of the interpolated list + if len(fcstList) > 0: + if (testMode and (f['validTime'] >= selectedStartTime and \ + f['validTime'] < selectedEndTime)) or (not testMode): + interpFcstList.append(fcstList[-1]) if len(fcstList) == 1: interpFcstList = fcstList @@ -1549,47 +1843,74 @@ class Procedure (SmartScript.SmartScript): self.statusBarMsg("No cyclone forecasts found within the Selected TimeRange", "S") else: + # If the wind grids are more than 3 hours long, the first grid ends up being double + # duration. So, add an extra duplicate forecast at the beginning and reset + # the validTime + print "tcHours:", tcHours + if tcHours > 3: + interpFcstList.insert(0, copy.deepcopy(interpFcstList[0])) + interpFcstList[0]["validTime"] = (int(interpFcstList[0]["validTime"] / tcDuration) \ + * tcDuration) + interpFcstList[1]["validTime"] = (int(interpFcstList[0]["validTime"] / tcDuration) \ + * tcDuration) + tcDuration + print "Adjusted time for first forecast" print "Generating", len(interpFcstList), "wind grids" - + # get the lat, lon grids latGrid, lonGrid = self.getLatLonGrids() - + + self._applyNCSCorrection = False + if varDict["Reduce Radii by 15% or \n NC State Bias Correction"] == "Reduce by 15%": + # Reduce the extent of the wind radii per Mark De Maria's research + # Loop through each wind radius and modify in place + for f in interpFcstList: + for windValue in f["radii"]: + for i in range(len(f["radii"][windValue])): + f["radii"][windValue][i] = f["radii"][windValue][i] * 0.85 + elif varDict["Reduce Radii by 15% or \n NC State Bias Correction"] == "NC State Bias Correction": + self._applyNCSCorrection = True + # make a grid for each interpolate forecast gridCount = 0 for f in interpFcstList: - t1 = time.time() - windGrid = self.makeRankine(f, latGrid, lonGrid, pieSlices, radiiFactor) - print "time to makeRankine:", time.time() - t1 + + self._timeRange = timeRange validTime = int(f['validTime'] / 3600) * 3600 bgGrid = self.getClosestWindGrid(bgModelName, bgDict, validTime) + startTime = validTime + endTime = validTime + (interval * 3600) + timeRange = self.makeTimeRange(startTime, endTime) + self._cycloneTimeRange = timeRange - start = AbsTime.AbsTime(int(validTime)) - timeRange = TimeRange.TimeRange(start, start + interval * 3600) + t1 = time.time() + windGrid = self.makeRankine(f, latGrid, lonGrid, pieSlices, RADII_FACTOR, timeRange) + print "Time to makeRankine:", time.time() - t1 - grid = self.blendGrids(windGrid, bgGrid) + magGrid, dirGrid = self.blendGrids(windGrid, bgGrid) + magGrid = self.smoothGrid(magGrid, 5) + dirGrid = self.smoothDirectionGrid(dirGrid, 5) name = "Wind" - self.createGrid("Fcst", name, "VECTOR", grid, timeRange, + self.createGrid("Fcst", name, "VECTOR", (magGrid, dirGrid), timeRange, descriptiveName=None, timeConstraints=None, precision=1, minAllowedValue=0.0, maxAllowedValue=200.0) - - gridCount += 1 - print "TCMWindTool:", productID, "- Generated",gridCount, \ - "out of", len(interpFcstList), "grids", time.asctime(time.gmtime(timeRange.startTime().unixTime())) + + gridCount = gridCount + 1 + print "TCMWindTool:", productID, "- Generated", gridCount, \ + "out of", len(interpFcstList), "grids", \ + time.asctime(time.gmtime(timeRange.startTime().unixTime())) # interpolate through forecast period to very high resolution and make # a composite maxWind grid from those wind grids if maxwindswath is "Yes": t1 = time.time() self.makeMaxWindGrid(interpFcstList, interval, latGrid, lonGrid, pieSlices, - radiiFactor) + RADII_FACTOR) print time.time() - t1, "seconds to generate Max wind composite." if msg != "": self.statusBarMsg(msg, "S") - + return None - - diff --git a/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/DefineMaxWindGUI.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/DefineMaxWindGUI.py new file mode 100644 index 0000000000..df96b09244 --- /dev/null +++ b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/utilities/DefineMaxWindGUI.py @@ -0,0 +1,225 @@ +# ---------------------------------------------------------------------------- +# This software is in the public domain, furnished "as is", without technical +# support, and with no warranty, express or implied, as to its usefulness for +# any purpose. +# +# DefineMaxWindGUI +# +# Author: +# ---------------------------------------------------------------------------- + +try: # See if this is the AWIPS I environment + from Numeric import * + import AFPS + AWIPS_ENVIRON = "AWIPS1" +except: # Must be the AWIPS II environment + from numpy import * + import AbsTime + import TimeRange + AWIPS_ENVIRON = "AWIPS2" + +import SmartScript +import Tkinter +import tkFont + + +class DefineMaxWindGUI(SmartScript.SmartScript): + + def __init__(self, dbss, eaMgr=None): + SmartScript.SmartScript.__init__(self, dbss) + + if AWIPS_ENVIRON == "AWIPS1": + self.setUp(eaMgr) + + def reportQuadError(self): + self.statusBarMsg("Only three quadants at a time may be reduced.\n" + \ + "...Please toggle another quadrant off first.","S") + return + + def toggleButton(self, buttonLabel): + b = self._buttonLabels.index(buttonLabel) + if self._buttonState[b]: # button was on + self._buttonState[b] = False + self._buttonList[b].configure(background="gray", activebackground="gray") + else: # button was off + if sum(self._buttonState) > 2: # only three at a time allowed + self.reportQuadError() + return + self._buttonState[b] = True + self._buttonList[b].configure(background="green", activebackground="green") + + + def NEClick(self): + self.toggleButton("NE") + return + + def SEClick(self): + self.toggleButton("SE") + return + + def SWClick(self): + self.toggleButton("SW") + return + + def NWClick(self): + self.toggleButton("NW") + return + + def makeLabel(self, frame): + + label = Tkinter.Label(frame, fg="red", font=self._boldFont, + text="Max winds will be reduced by\n 20% in selected quadrants") + label.grid(row=0) + + return + + def makeBottomButtons(self, frame): + # Create the Execute/Cancel buttons + self._doneButton = Tkinter.Button(frame, text="Done", + command=self.doneCommand) + self._doneButton.grid(row=3, column=0, padx=20, pady=5, sticky=Tkinter.W) + + self._cancelButton = Tkinter.Button(frame, text="Cancel", + command=self.cancelCommand) + self._cancelButton.grid(row=3, column=2, padx=20, pady=5, sticky=Tkinter.E) + + frame.grid(columnspan=3, sticky=Tkinter.EW) + + return + + def makeQuadButtons(self, frame): + # Create the quadrant buttons + commandList = [self.NWClick, self.SWClick, self.SEClick, self.NEClick] + self._buttonLabels = ["NW", "SW", "SE", "NE"] + + # define the button position in geometric order + buttonPos = [(0, 0), (1, 0), (1, 1), (0, 1)] + for b in range(len(self._buttonLabels)): + label = self._buttonLabels[b] + + self._buttonList[b] = Tkinter.Button(frame, text=label, + command=commandList[b], + font=self._bigFont, width=3) + rowPos, colPos = buttonPos[b] + self._buttonList[b].grid(row=rowPos, column=colPos, padx=30, pady=10) + + return + + def setUpUI(self): + + self._labelFrame = Tkinter.Frame(self._master) + + self._labelFrame.grid(row=0) + + + self._buttonFrame = Tkinter.Frame(self._master, borderwidth=3, + relief=Tkinter.RIDGE, bd=2, pady=5) + self._buttonFrame.grid(row=1, column=0,padx=25, + sticky=Tkinter.E+Tkinter.W, pady=5) + + + self._bottomFrame = Tkinter.Frame(self._master, borderwidth=3, + relief=Tkinter.RIDGE, bd=2) + self._bottomFrame.grid(row=2, column=0, columnspan=2, padx=25) + + self._master.title("Reduce Max Wind by Quadrant") + + self.makeLabel(self._labelFrame) + + self.makeQuadButtons(self._buttonFrame) + + self.makeBottomButtons(self._bottomFrame) + + return + + def doneCommand(self): + self._master.quit() + + quadCount = 4 + reducePct = 0.80 + + # Gather up the maxWind info to return to the main tool + self._maxWindDict = {} + for h in range(len(self._hourList)): + windList = [] + for quad in range(quadCount): + + # Reduce the value if that quadrant was selected + if self._buttonState[quad]: + windValue = self._maxWind[quad][h] * self._allTimeMaxWind * reducePct + else: + windValue = self._maxWind[quad][h] * self._allTimeMaxWind + + windList.append(windValue) + + windList.reverse() + self._maxWindDict[self._hourList[h]] = windList + + return + + def cancelCommand(self): + self._master.destroy() + + return None + + def displayGUI(self, windDict): + + self._windDict = windDict + self._maxWindDict = None + self._quadCount = 4 + + hourKeys = self._windDict.keys() + hourKeys.sort() + self._hourList = hourKeys + self._initialMinWind = [] + self._initialMaxWind = [] + for hour in hourKeys: + minWind, maxWind = windDict[hour] + self._initialMinWind.append(minWind) + self._initialMaxWind.append(maxWind) + + self._hourCount = len(hourKeys) + + if AWIPS_ENVIRON == "AWIPS1": + self._allTimeMaxWind = max(self._initialMaxWind) + else: # numpy uses "amax" + self._allTimeMaxWind = amax(self._initialMaxWind) + + + + # Make the topLevel window - different for A1 and A2 + if AWIPS_ENVIRON == 'AWIPS1': + self._master = Tkinter.Toplevel(self.eaMgr().root()) + self._master.transient(self.eaMgr().root()) # always draw on top of GFE + else: + self._tkmaster = Tkinter.Tk() + self._master = Tkinter.Toplevel(self._tkmaster) + self._tkmaster.withdraw() + + self._buttonLabels = ["NW", "SW", "SE", "NE"] + + self._buttonState = [False, False, False, False] + self._buttonList = [None, None, None, None] + + self._boldFont = tkFont.Font(family="Helvetica", size=12, weight="bold") + self._bigFont = tkFont.Font(family="Helvetica", size=16) + + self.setUpUI() + + self._maxWind = zeros((self._quadCount, self._hourCount)) * 1.0 + + for hour in range(len(hourKeys)): + for quad in range(self._quadCount): + minWind, maxWind = self._windDict[hourKeys[hour]] + self._maxWind[quad][hour] = maxWind / self._allTimeMaxWind + + + #self.updateDisplay() # Draws everything + + + self._master.mainloop() + + self._master.withdraw() + self._master.destroy() + + return self._maxWindDict From daddc33728991d1ad6bf2ec02d9ca94ff7a3281d Mon Sep 17 00:00:00 2001 From: "Shawn.Hooper" Date: Fri, 27 Feb 2015 14:17:34 -0500 Subject: [PATCH 26/34] ASM #17172 - Update heap size to 1536M for 14.3.1.1 64-bit Change-Id: Iea8841e91a5d4ea005fecd0e89cd55d67319db1c Former-commit-id: a28a9e18b06d58bec229523d15d6c2f474bff6a2 [formerly c96e25664a8ef82f33bbad3ea82efad90024baee] Former-commit-id: ef3fe69c5fe4938453a443b20c414cc582228060 --- cave/com.raytheon.viz.product.awips/awips.product | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cave/com.raytheon.viz.product.awips/awips.product b/cave/com.raytheon.viz.product.awips/awips.product index ffa33d0a95..2bab084cf2 100644 --- a/cave/com.raytheon.viz.product.awips/awips.product +++ b/cave/com.raytheon.viz.product.awips/awips.product @@ -33,7 +33,7 @@ -Dthrift.stream.maxsize=200 -Dviz.memory.warn.threshold=98 -Xmx1280M -XX:MaxDirectMemorySize=2G - -Dfile.encoding=UTF-8 -Xmx768M + -Dfile.encoding=UTF-8 -Xmx1536M From 5258c8f8b7252a23419bd7e034c4ad2ba17cde26 Mon Sep 17 00:00:00 2001 From: "Qinglu.Lin" Date: Fri, 27 Feb 2015 16:10:22 -0500 Subject: [PATCH 27/34] ASM #17169: WarnGen Templates: IBW marine CTA and impact updates Change-Id: I59e000340b6a82dc439128f3ad6963476b669344 Former-commit-id: dcef82c4d6bce62965d5f01f447834cfd3c2b043 [formerly 441be68f8726618225b0c7a5077443b8a7815d99] Former-commit-id: cf66bb11f30ee58edce75d9d66233f8c31269ec1 --- .../warngen/impactSpecialMarineWarning.vm | 38 ++--- .../warngen/impactSpecialMarineWarning.xml | 25 ++-- .../impactSpecialMarineWarningFollowup.vm | 134 ++++++++++-------- .../impactSpecialMarineWarningFollowup.xml | 25 ++-- .../base/warngen/impactStatements.vm | 61 +++++++- 5 files changed, 179 insertions(+), 104 deletions(-) diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.vm b/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.vm index f6a661f5fe..0647bce760 100755 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.vm +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.vm @@ -6,6 +6,8 @@ ## EVAN BOOKBINDER -- SEP 18 2013 Implemented config.vm ## PHIL KURIMSKI -- JUN 26 2014 OB14.2.2-9 ## ## ADDED DSS EVENTS ## +## UPDATED PHIL KURIMSKI -- FEB 18 2015 Implemented## +## updated CTAs ## ##################################################### #parse("config.vm") ################################################ @@ -335,7 +337,7 @@ THIS IS A TEST MESSAGE. ## THIS IS A TEST MESSAGE. ## #end #thirdBullet(${dateUtil},${event},${timeFormat},${localtimezone},${secondtimezone}) -...${report}LOCATED ## +...${report}WAS LOCATED ## #if(${stormType} == "line") #handleClosestPoints(${list}, ${closestPoints}, ${otherClosestPoints}, ${stormType}, ${nearPhrase} , ${maxMarineNearDistance}, ${overPhrase}, ${maxMarineOverDistance}, ${marineDistanceUnits}, ${useSecondReferenceLine}) #else @@ -406,7 +408,7 @@ THOSE ATTENDING !**event/venue name or location**! ARE IN THE PATH OF ${specialE ## Comment out #parse command below to pull in Dynamic DSS Event Info ## If this feature is utilized, the "specialEvent" bullet (output above) can -## likely be commented out from the impactSevereThunderstormWarning.xml file +## likely be commented out from the impactSpecialMarineWarning.xml file #parse("dssEvents.vm") ##################### @@ -430,46 +432,46 @@ PRECAUTIONARY/PREPAREDNESS ACTIONS... ${ashfallCTA} #if(${list.contains(${bullets}, "genericCTA")}) -#if(${stormType} == "line") -AS THUNDERSTORMS MOVE OVER THE WATER...BOATERS CAN EXPECT GUSTY WINDS AND HIGH WAVES. MOVE TO SAFE HARBOR OR STAY CLOSE TO SHORE UNTIL THESE STORMS PASS. +MOVE TO SAFE HARBOR UNTIL HAZARDOUS WEATHER PASSES. -#else -AS THIS THUNDERSTORM MOVES OVER THE WATER...BOATERS CAN EXPECT GUSTY WINDS AND HIGH WAVES. MOVE TO SAFE HARBOR OR STAY CLOSE TO SHORE UNTIL THE STORM PASSES. - -#end #end #if(${list.contains(${bullets}, "gustyWindsCTA")}) -#if(${stormType} == "line") -MARINERS CAN EXPECT GUSTY WINDS...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAINS. BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THESE STORMS PASS. +MOVE TO SAFE HARBOR IMMEDIATELY AS GUSTY WINDS AND HIGH WAVES ARE EXPECTED. -#else -MARINERS CAN EXPECT GUSTY WINDS...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAINS. BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THIS STORM PASSES. - -#end #end #if(${list.contains(${bullets}, "hailWindsCTA")}) #if(${stormType} == "line") -MARINERS CAN EXPECT ${windCTA}${hailCTA}...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAINS. BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THESE STORMS PASS. +BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THESE STORMS PASS. ${windCTA}${hailCTA}...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAIN ARE POSSIBLE WITH THESE STORMS. #else -MARINERS CAN EXPECT ${windCTA}${hailCTA}...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAINS. BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THIS STORM PASSES. +BOATERS SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THIS STORM PASSES. ${windCTA}${hailCTA}...HIGH WAVES...DANGEROUS LIGHTNING...AND HEAVY RAIN ARE POSSIBLE WITH THIS STORM. #end #end #if(${list.contains(${bullets}, "nonThunderstormCTA")}) -MARINERS CAN EXPECT GUSTY WINDS...AND INCREASING WAVES. BOATERS...ESPECIALLY THOSE UNDER SAIL...SHOULD SEEK SAFE HARBOR IMMEDIATELY...UNTIL THE HIGH WINDS SUBSIDE. +SEEK SAFE SHELTER...MARINERS CAN EXPECT GUSTY WINDS...AND INCREASING WAVES. #end #if(${list.contains(${bullets}, "waterspoutCTA")}) +#if(${list.contains(${bullets}, "thunderstorm")}) THUNDERSTORMS CAN PRODUCE SUDDEN WATERSPOUTS. WATERSPOUTS CAN EASILY OVERTURN BOATS AND CREATE LOCALLY HAZARDOUS SEAS. SEEK SAFE HARBOR IMMEDIATELY. +#else +WATERSPOUTS CAN EASILY OVERTURN BOATS AND CREATE LOCALLY HAZARDOUS SEAS. SEEK SAFE HARBOR IMMEDIATELY. + +#end #end #if(${list.contains(${bullets}, "lightningCTA")}) +#if(${stormType} == "line") +FREQUENT LIGHTNING IS OCCURRING WITH THESE STORMS. IF CAUGHT ON THE OPEN WATER STAY BELOW DECK IF POSSIBLE...KEEP AWAY FROM UNGROUNDED METAL OBJECTS. + +#else FREQUENT LIGHTNING IS OCCURRING WITH THIS STORM. IF CAUGHT ON THE OPEN WATER STAY BELOW DECK IF POSSIBLE...KEEP AWAY FROM UNGROUNDED METAL OBJECTS. +#end #end #if(${list.contains(${bullets}, "reportCTA")}) -REPORT SEVERE WEATHER TO THE COAST GUARD OR NEAREST LAW ENFORCEMENT AGENCY. THEY WILL RELAY YOUR REPORT TO THE NATIONAL WEATHER SERVICE OFFICE. +REPORT SEVERE WEATHER TO THE NATIONAL WEATHER SERVICE OFFICE. #end #if(${ctaSelected} == "YES") diff --git a/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.xml b/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.xml index ae6317efd2..e480adcb15 100755 --- a/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.xml +++ b/edexOsgi/com.raytheon.uf.common.dataplugin.warning/utility/common_static/base/warngen/impactSpecialMarineWarning.xml @@ -5,6 +5,7 @@ Note: Volcano information will not show up in the GUI unless uncommented out Phil Kurimski 09-19-2013 added geospatialConfig.xml Phil Kurimski 06-26-2014 added DSS Events + Phil Kurimski 02-18-2015 Updated CTA section --> - MRMS + MRMS_1000
161
0 10000 @@ -22,7 +22,7 @@
- MRMS + MRMS_0500
161
0 10001 @@ -58,4 +58,4 @@ - \ No newline at end of file + diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS1kmClip.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS1kmClip.xml index 26c8945671..c2eff68650 100755 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS1kmClip.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS1kmClip.xml @@ -1,6 +1,6 @@ - MRMS + MRMS_1000 10000 800 600 diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS500mClip.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS500mClip.xml index 5f2fc8f358..c39b65aabb 100755 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS500mClip.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/subgrids/MRMS500mClip.xml @@ -1,6 +1,6 @@ - MRMS + MRMS_0500 10001 1600 1200 diff --git a/edexOsgi/com.raytheon.uf.edex.plugin.grid/utility/common_static/base/purge/gridPurgeRules.xml b/edexOsgi/com.raytheon.uf.edex.plugin.grid/utility/common_static/base/purge/gridPurgeRules.xml index b387c541de..d4b57c33ef 100644 --- a/edexOsgi/com.raytheon.uf.edex.plugin.grid/utility/common_static/base/purge/gridPurgeRules.xml +++ b/edexOsgi/com.raytheon.uf.edex.plugin.grid/utility/common_static/base/purge/gridPurgeRules.xml @@ -808,7 +808,12 @@ - MRMS + MRMS_1000 + 120 + 03-00:00:00 + + + MRMS_0500 120 03-00:00:00 From 304ec5502d3e2c989833dd3c4bd3a85302089acb Mon Sep 17 00:00:00 2001 From: "Sean.Webb" Date: Tue, 10 Mar 2015 12:43:15 -0400 Subject: [PATCH 33/34] ASM #17227 - Fixed total lightning pattern Change-Id: I889a405685e1b51cb115d40ada425f80947920d2 Former-commit-id: 4ef6100cf8fe349422d72d3112740160e464aa0f [formerly 4349757b615d3963d3201c1045fa7599c09acdf8] Former-commit-id: 20ea80a89a96a35fa763add52d8c2a5078b8321b --- rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template b/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template index 2ccaf5a920..7162ec4f39 100644 --- a/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template +++ b/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template @@ -387,8 +387,8 @@ HDS ^(SF(US|PA)41) ([A-Z]{4}) (..)(..)(..) # WMO Heading for Earth Networks Total Lightning -HDS ^(SF(US|PA)42) ([A-Z]{4}) (..)(..)(..) - FILE -overwrite -log -edex -close /data_store/entlightning/(\4:yyyy)(\4:mm)\4/\5/\1_\3_\4\5\6_(seq).nldn.%Y%m%d%H +NGRID ^(SFPA42) ([A-Z]{4}) (..)(..)(..) + FILE -overwrite -log -edex -close /data_store/entlightning/(\4:yyyy)(\4:mm)\4/\5/\1_\3_\4\5\6_(seq).%Y%m%d%H # AWIPS1: TEXT ^[ABCFMNRSUVW]......[KPTMC] /text/NO_STORE # TEXT ^[ABCFMNRSUVW].....[KPTMC] /text/NO_STORE From 006d437d1786330afca364d04363d0d07204ee27 Mon Sep 17 00:00:00 2001 From: Michael Gamazaychikov Date: Tue, 10 Mar 2015 13:17:29 -0400 Subject: [PATCH 34/34] ASM #629 - GFE: when runProcedure terminates unexpectedly locks remain in gfelocktable. Change-Id: I7b0f6b3974e8a8e4072fa530dce7443a9d85244a Former-commit-id: 12304947c6e206ad24757ea126876833d74269f6 [formerly a437417c6a07fc069e194cd08d515c6984d15616] Former-commit-id: 24d10369ab630f559c698e31e2d0232beea017d1 --- .../edex/plugin/gfe/server/lock/ClearGfeOrphanedLocks.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/server/lock/ClearGfeOrphanedLocks.java b/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/server/lock/ClearGfeOrphanedLocks.java index 85bbe5253c..3d803403fc 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/server/lock/ClearGfeOrphanedLocks.java +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/server/lock/ClearGfeOrphanedLocks.java @@ -144,10 +144,10 @@ public class ClearGfeOrphanedLocks { lockList = (List) lockMgr.getAllLocks(siteId); // find orphaned locks and break them breakLocks(clients, lockList, lockMgr, siteId); - return; } catch (GfeException e) { statusHandler.error("Error retrieving all locks", e); } } + return; } }