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()); } } } diff --git a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsAction.java b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsAction.java index d2d1b68db1..53c2266bfe 100644 --- a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsAction.java +++ b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsAction.java @@ -48,7 +48,7 @@ import com.raytheon.viz.ui.EditorUtil; * ------------ ---------- ----------- -------------------------- * May 2009 # bsteffen Initial creation * Nov 2013 # mccaslin Only one GUI dialog at a time - * Oct 2014 # mccaslin Improved error handeling + * Oct 2014 # mccaslin Improved error handeling * * * @@ -56,7 +56,8 @@ import com.raytheon.viz.ui.EditorUtil; * @version 1.0 */ public class LapsToolsAction extends AbstractHandler { - private static final transient IUFStatusHandler statusHandler = UFStatus.getHandler(LapsToolsAction.class); + private static final transient IUFStatusHandler statusHandler = UFStatus + .getHandler(LapsToolsAction.class); /** * LAPS Tools dialog. @@ -65,50 +66,49 @@ public class LapsToolsAction extends AbstractHandler { public static LAPSToolsDlg getLapsToolsDlg() { return lapsToolsDlg; - } + } public Object execute(ExecutionEvent arg0) throws ExecutionException { Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow() .getShell(); - + if (lapsToolsDlg == null) { try { - lapsToolsDlg = new LAPSToolsDlg(shell); - lapsToolsDlg.addListener(SWT.Dispose, new Listener() { - @Override - public void handleEvent(Event event) { - lapsToolsDlg = null; - } - }); - - if (lapsToolsDlg.isLapsInstalled()) { - lapsToolsDlg.open(); - } else { - String whatLapsIs ="LAPS data assimilation system, system requirements: " + - "\n\to LAPS v2.0 installed" + - "\n\to EDEX 'SITE' file containing LAPS domain, i.e. domain.xml" + - "\n\n\n(Sorry LAPS does not work in the ADAM implementation of AWIPS.)"; - //Note: Go through the LAPS v2.0 Scripting Interface first, if you find that LAPS is not installed. - - MessageBox mb = new MessageBox(EditorUtil.getActiveEditor().getSite().getShell(), SWT.ICON_WARNING | SWT.OK); - mb.setText("Cannot open LAPS V2.0 Tools GUI"); - mb.setMessage(whatLapsIs); - mb.open(); - lapsToolsDlg = null; + lapsToolsDlg = new LAPSToolsDlg(shell); + lapsToolsDlg.addListener(SWT.Dispose, new Listener() { + @Override + public void handleEvent(Event event) { + lapsToolsDlg = null; + } + }); - //int val = mb.open(); - //if (val == SWT.OK) { - // AlertViz Customization Update - //return false; - //} + if (lapsToolsDlg.isLapsInstalled()) { + lapsToolsDlg.open(); + } else { + String whatLapsIs = "LAPS is not installed. "; + // Note: Go through the LAPS v2.0 Scripting Interface first, + // if you find that LAPS is not installed. - } + MessageBox mb = new MessageBox(EditorUtil.getActiveEditor() + .getSite().getShell(), SWT.ICON_ERROR | SWT.OK); + mb.setText("Cannot open the LAPS tool"); + mb.setMessage(whatLapsIs); + mb.open(); + lapsToolsDlg = null; + + // int val = mb.open(); + // if (val == SWT.OK) { + // AlertViz Customization Update + // return false; + // } + + } + + } catch (VizException e) { + statusHandler.handle(Priority.PROBLEM, + "Error: Cannot open LAPS V2.0 Tools GUI", e); + } - } catch (VizException e) { - statusHandler.handle(Priority.PROBLEM, - "Error: Cannot open LAPS V2.0 Tools GUI", e); - } - } return null; diff --git a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsIO.java b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsIO.java index a892d530d9..641149fa20 100644 --- a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsIO.java +++ b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/action/LapsToolsIO.java @@ -5,6 +5,7 @@ import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; import java.io.IOException; +import java.io.OutputStream; import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.Collection; @@ -27,6 +28,7 @@ import com.raytheon.uf.common.localization.LocalizationContext.LocalizationLevel import com.raytheon.uf.common.localization.LocalizationContext.LocalizationType; import com.raytheon.uf.common.localization.LocalizationFile; import com.raytheon.uf.common.localization.PathManagerFactory; +import com.raytheon.uf.common.localization.exception.LocalizationException; import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.common.time.SimulatedTime; @@ -36,8 +38,8 @@ import com.raytheon.viz.awipstools.ui.action.LapsToolsData.LapsDomain; import com.vividsolutions.jts.geom.Envelope; /** - * This class no longer performs all the file system, server input/output for laps. - * LAPS support scripts now conduct the localization process. + * This class no longer performs all the file system, server input/output for + * laps. LAPS support scripts now conduct the localization process. * *
  * 
@@ -55,147 +57,150 @@ import com.vividsolutions.jts.geom.Envelope;
  */
 public class LapsToolsIO {
 
-    private static final transient IUFStatusHandler statusHandler = UFStatus.getHandler(LapsToolsIO.class);
+    private static final transient IUFStatusHandler statusHandler = UFStatus
+            .getHandler(LapsToolsIO.class);
 
     private static final String WHATGOTIN_FILE_FRMT = "%s/%s.wgi";
 
     private static File fxaData;
 
     private static File lapsLogs;
-    
+
     private static List whatgotinFiles;
 
     private static List dataChoices;
-    
 
     static {
         // TODO all this configuration should be customizable by the user.
-    	// --- For what got in log files ---
+        // --- For what got in log files ---
         if (System.getenv("FXA_DATA") == null) {
-            fxaData = new File("/data/fxa"); 
+            fxaData = new File("/data/fxa");
         } else {
             fxaData = new File(System.getenv("FXA_DATA"));
         }
         lapsLogs = new File(fxaData + "/laps/log/wgi");
-        whatgotinFiles = Arrays.asList("sfc", "wind", "lq3driver", "cloud", "temp");
-        dataChoices = Arrays.asList("Surface Analysis", 
-        		"Wind Analysis", 
-        		"Humidity Analysis", 
-        		"Cloud Analysis",
-                "Temperature Analysis");
+        whatgotinFiles = Arrays.asList("sfc", "wind", "lq3driver", "cloud",
+                "temp");
+        dataChoices = Arrays.asList("Surface Analysis", "Wind Analysis",
+                "Humidity Analysis", "Cloud Analysis", "Temperature Analysis");
     }
 
     public static Collection getDataChoices() {
         return dataChoices;
     }
 
-    public static String getLogs(String type) throws IOException,
-            VizException {
+    public static String getLogs(String type) throws IOException, VizException {
         String wgiFile = String.format(WHATGOTIN_FILE_FRMT, lapsLogs,
-                    whatgotinFiles.get(dataChoices.indexOf(type)));
+                whatgotinFiles.get(dataChoices.indexOf(type)));
         String resultIO = loadWhatGotInFile(wgiFile, type);
-        return (resultIO); 
+        return (resultIO);
     }
-    
-    private static String loadWhatGotInFile(String wfile, String type) 
-    		throws IOException {
-    	BufferedReader reader;
-        StringBuilder output = new StringBuilder();
-		File file = new File(wfile);
 
-    	try {
-    		reader = new BufferedReader(new FileReader(file));
-    	} catch (FileNotFoundException e) {
-            String arg = String.format("*** Cannot find expected log file for %s." +
-            		"\n*** File %s ....does not appear to exist.\n\n", 
-            		type, file.getAbsolutePath());
-    		output.append(arg);          
-    		return output.toString();
-    	}
+    private static String loadWhatGotInFile(String wfile, String type)
+            throws IOException {
+        BufferedReader reader;
+        StringBuilder output = new StringBuilder();
+        File file = new File(wfile);
+
+        try {
+            reader = new BufferedReader(new FileReader(file));
+        } catch (FileNotFoundException e) {
+            String arg = String
+                    .format("*** Cannot find expected log file for %s."
+                            + "\n*** File %s ....does not appear to exist.\n\n",
+                            type, file.getAbsolutePath());
+            output.append(arg);
+            return output.toString();
+        }
         String line;
         int lineNumber = 0;
         while ((line = reader.readLine()) != null) {
             String arg = String.format("%04d: %s%n", ++lineNumber, line);
-    		output.append(arg);
+            output.append(arg);
         }
         reader.close();
         return output.toString();
     }
-    
+
     public static LapsToolsData loadData() throws IOException, VizException,
             SpatialException {
         LapsToolsData data = new LapsToolsData();
-        if(LapsToolsIO.readXmlFile(data)) {
-        	LapsToolsIO.readCountyWarningArea(data);
+        if (LapsToolsIO.readXmlFile(data)) {
+            LapsToolsIO.readCountyWarningArea(data);
         } else {
-        	data = null;
+            data = null;
         }
         return data;
     }
-    
-    public static boolean readXmlFile(LapsToolsData data) 
-    		throws IOException, VizException {
-        
+
+    public static boolean readXmlFile(LapsToolsData data) throws IOException,
+            VizException {
+
         IPathManager pm = PathManagerFactory.getPathManager();
-        LocalizationContext lc = pm.getContext(LocalizationType.COMMON_STATIC, 
-        		LocalizationLevel.SITE);
-		LocalizationFile xmlLocalizationFile = pm.getLocalizationFile(lc, "LAPS/domain" +
-				".xml");     
-		if(!xmlLocalizationFile.exists()) return false;	
-		
-		LapsDomain domain = new LapsDomain();
-		try {
-			JAXBContext jaxbContext = JAXBContext.newInstance(LapsDomain.class);
-	    	//unmarshal XML file to java object
-			Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();	
-			domain = (LapsDomain) jaxbUnmarshaller.unmarshal(xmlLocalizationFile.getFile());
-			
-		} catch (JAXBException e) {
-			throw new VizException("xml is unreadable: "+e.getLocalizedMessage());
-		}	
-		
+        LocalizationContext lc = pm.getContext(LocalizationType.COMMON_STATIC,
+                LocalizationLevel.SITE);
+        LocalizationFile xmlLocalizationFile = pm.getLocalizationFile(lc,
+                "LAPS/domain" + ".xml");
+        if (!xmlLocalizationFile.exists())
+            return false;
+
+        LapsDomain domain = new LapsDomain();
+        try {
+            JAXBContext jaxbContext = JAXBContext.newInstance(LapsDomain.class);
+            // unmarshal XML file to java object
+            Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
+            domain = (LapsDomain) jaxbUnmarshaller
+                    .unmarshal(xmlLocalizationFile.getFile());
+
+        } catch (JAXBException e) {
+            throw new VizException("xml is unreadable: "
+                    + e.getLocalizedMessage());
+        }
+
         data.setNx(domain.getNx());
         data.setNy(domain.getNy());
         data.setNz(domain.getNz());
         data.setGridSpacing(domain.getGridSpacing());
         data.setGridCenterLon(domain.getGridCenLon());
-        data.setGridCenterLat(domain.getGridCenLat()); 
+        data.setGridCenterLat(domain.getGridCenLat());
         return true;
     }
 
-    public static void defaultDomain(LapsToolsData data)
-            throws IOException, VizException {
-    	double distance = 111; //111 km per 1 degree
-    	double gridSpacingDefault = 3500.;
+    public static void defaultDomain(LapsToolsData data) throws IOException,
+            VizException {
+        double distance = 111; // 111 km per 1 degree
+        double gridSpacingDefault = 3500.;
         double dim = 200;
         double N = dim * dim;
-        
+
         Envelope shapeArea = data.getValidAreaOrig();
         double widthY = Math.abs(shapeArea.getHeight() * distance);
-        double widthX = Math.abs( (shapeArea.getWidth() * distance) * (Math.cos(shapeArea.centre().x) ) );
+        double widthX = Math.abs((shapeArea.getWidth() * distance)
+                * (Math.cos(shapeArea.centre().x)));
         double aspectRatio = widthY / widthX;
         double buffer = 0.5;
-        int nX = (int) (Math.sqrt(N/aspectRatio) + buffer);
+        int nX = (int) (Math.sqrt(N / aspectRatio) + buffer);
         int nY = (int) ((aspectRatio * nX) + buffer);
-        
-        System.out.print("LAPS Tools IO:\nheight = "+shapeArea.getHeight()+ " width = "+shapeArea.getWidth());
-        System.out.print("\naspect ratio = "+aspectRatio);
-        System.out.print("\nnX = "+nX+", nY = "+nY+"\n");  
 
-		LapsDomain domain = new LapsDomain();
+        System.out.print("LAPS Tools IO:\nheight = " + shapeArea.getHeight()
+                + " width = " + shapeArea.getWidth());
+        System.out.print("\naspect ratio = " + aspectRatio);
+        System.out.print("\nnX = " + nX + ", nY = " + nY + "\n");
+
+        LapsDomain domain = new LapsDomain();
         domain.setNx(nX);
         domain.setNy(nY);
         domain.setNz(43);
         domain.setGridSpacing(gridSpacingDefault);
         domain.setGridCenLon(data.getCwaCenter().x);
-        domain.setGridCenLat(data.getCwaCenter().y);  
-        
+        domain.setGridCenLat(data.getCwaCenter().y);
+
         data.setNx(domain.getNx());
         data.setNy(domain.getNy());
         data.setNz(domain.getNz());
         data.setGridSpacing(domain.getGridSpacing());
         data.setGridCenterLon(data.getCwaCenter().x);
-        data.setGridCenterLat(data.getCwaCenter().y);      
+        data.setGridCenterLat(data.getCwaCenter().y);
     }
 
     private static void readCountyWarningArea(LapsToolsData data)
@@ -209,23 +214,25 @@ public class LapsToolsIO {
             return;
         }
         data.setCwaArea(result[0].geometry.getEnvelopeInternal());
-    }    
-    
+    }
+
     public static String getWriteXmlQuestion() {
         String date = new SimpleDateFormat("HH:mm").format(SimulatedTime
                 .getSystemTime().getTime());
-        return String.format("Are you sure you want write domain.xml?" +
-        		"\nNote: Its %s and LAPS runs at ~20 minutes after the hour.",date);
+        return String
+                .format("Are you sure you want write domain.xml?"
+                        + "\nNote: Its %s and LAPS runs at ~20 minutes after the hour.",
+                        date);
     }
 
     public static void writeXmlFile(LapsToolsData data) throws IOException,
             InterruptedException, VizException {
-    	
-    	IPathManager pm = PathManagerFactory.getPathManager();
-		LocalizationContext lc = pm.getContext(LocalizationType.COMMON_STATIC,
-				LocalizationLevel.SITE);
-		LocalizationFile xmlLocalizationFile = pm.getLocalizationFile(lc, "LAPS/domain-output" +
-				".xml");        
+
+        IPathManager pm = PathManagerFactory.getPathManager();
+        LocalizationContext lc = pm.getContext(LocalizationType.COMMON_STATIC,
+                LocalizationLevel.USER);
+        LocalizationFile xmlLocalizationFile = pm.getLocalizationFile(lc,
+                "LAPS" + IPathManager.SEPARATOR + "domain.xml");
         LapsDomain lapsdomain = new LapsDomain();
         lapsdomain.setNx(data.getNx());
         lapsdomain.setNy(data.getNy());
@@ -233,7 +240,15 @@ public class LapsToolsIO {
         lapsdomain.setGridSpacing(data.getGridSpacing());
         lapsdomain.setGridCenLat(data.getGridCenter().y);
         lapsdomain.setGridCenLon(data.getGridCenter().x);
-    	//marshal java object to XML file
-        JAXB.marshal(lapsdomain, xmlLocalizationFile.getFile());
+        // marshal java object to XML file
+        OutputStream stream;
+        try {
+            stream = xmlLocalizationFile.openOutputStream();
+            JAXB.marshal(lapsdomain, stream);
+            stream.close();
+            xmlLocalizationFile.save();
+        } catch (LocalizationException e) {
+            throw new VizException("Unable to save LapsDomain to xml.");
+        }
     }
 }
diff --git a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/dialog/LAPSToolsDlg.java b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/dialog/LAPSToolsDlg.java
index 495e2c668c..c66ac57a44 100644
--- a/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/dialog/LAPSToolsDlg.java
+++ b/cave/com.raytheon.viz.awipstools/src/com/raytheon/viz/awipstools/ui/dialog/LAPSToolsDlg.java
@@ -77,8 +77,9 @@ import com.raytheon.viz.ui.editor.IMultiPaneEditor;
  * @version 1.0
  */
 public class LAPSToolsDlg extends CaveSWTDialog {
-	
-    private static final transient IUFStatusHandler statusHandler = UFStatus.getHandler(LAPSToolsDlg.class);
+
+    private static final transient IUFStatusHandler statusHandler = UFStatus
+            .getHandler(LAPSToolsDlg.class);
 
     private final LapsToolsData data;
 
@@ -109,7 +110,7 @@ public class LAPSToolsDlg extends CaveSWTDialog {
      */
     private boolean isLapsInstalled = false;
 
-	/**
+    /**
      * Flag indicating which tool is active.
      */
     private boolean isDataUsedByAnalysis = true;
@@ -175,9 +176,9 @@ public class LAPSToolsDlg extends CaveSWTDialog {
      */
     private StackLayout stackLayout;
 
-	private MessageBox areaDialog;
+    private MessageBox areaDialog;
 
-	private Label areaStrLbl;
+    private Label areaStrLbl;
 
     /**
      * Constructor.
@@ -192,16 +193,15 @@ public class LAPSToolsDlg extends CaveSWTDialog {
 
         try {
             this.data = LapsToolsIO.loadData();
-            if(data==null){
-            	isLapsInstalled = false;
+            if (data == null) {
+                isLapsInstalled = false;
             } else {
-            	isLapsInstalled = true;
+                isLapsInstalled = true;
             }
-            
+
         } catch (VizException e) {
-            MessageDialog
-                    .openInformation(shell, "LAPS Tools GUI cannot run.",
-                            e.getLocalizedMessage());
+            MessageDialog.openInformation(shell, "LAPS Tools GUI cannot run.",
+                    e.getLocalizedMessage());
             throw e;
         } catch (Exception e) {
             throw new VizException("Laps Dialog Failed to open", e);
@@ -242,8 +242,7 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         createMainControls();
 
         // create dialog with OK and cancel button and info icon
-        areaDialog = 
-          new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
+        areaDialog = new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
         areaDialog.setText("Size of LAPS Domain");
     }
 
@@ -352,27 +351,27 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         // Create the Help menu item with a Help "dropdown" menu
         Menu helpMenu = new Menu(menuBar);
         helpMenuItem.setMenu(helpMenu);
- 
+
         // create dialog with OK and cancel button and info icon
-        final MessageBox dialog = 
-          new MessageBox(shell, SWT.ICON_INFORMATION | SWT.OK);
+        final MessageBox dialog = new MessageBox(shell, SWT.ICON_INFORMATION
+                | SWT.OK);
         dialog.setText("About LAPS details");
-        dialog.setMessage("For additional detailed information about LAPS Tools go to the URL" +
-        		"\n\t http://laps.noaa.gov/awipsii/");
-        
+        dialog.setMessage("For additional detailed information about LAPS Tools go to the URL"
+                + "\n\t http://laps.noaa.gov/awipsii/");
+
         // ------------------------------------------------------
         // Create all the items in the Help dropdown menu
         // ------------------------------------------------------
 
-        // Administration menu item     
+        // Administration menu item
         MenuItem aboutMI = new MenuItem(helpMenu, SWT.NONE);
         aboutMI.setText("About LAPS...");
         aboutMI.addSelectionListener(new SelectionAdapter() {
             public void widgetSelected(SelectionEvent event) {
-            	dialog.open(); 
+                dialog.open();
             }
         });
-       
+
     }
 
     /**
@@ -430,22 +429,24 @@ public class LAPSToolsDlg extends CaveSWTDialog {
             @Override
             public void widgetSelected(SelectionEvent e) {
                 Combo c = (Combo) e.widget;
-                if(c.getSelectionIndex() == 0 && c.getItem(0) == "-- Select a Type --") {
-                	//no action
-                } else if(c.getSelectionIndex() != 0 && c.getItem(0) == "-- Select a Type --") {
-                	c.remove(0);
-                	typeAction((c.getItem(c.getSelectionIndex())));
+                if (c.getSelectionIndex() == 0
+                        && c.getItem(0) == "-- Select a Type --") {
+                    // no action
+                } else if (c.getSelectionIndex() != 0
+                        && c.getItem(0) == "-- Select a Type --") {
+                    c.remove(0);
+                    typeAction((c.getItem(c.getSelectionIndex())));
                 } else {
-                	typeAction((c.getItem(c.getSelectionIndex())));
+                    typeAction((c.getItem(c.getSelectionIndex())));
                 }
             }
         });
         typeCbo.select(0);
-        typeCbo.setToolTipText("Select one of the options to see what got into this LAPS product." );
-        
+        typeCbo.setToolTipText("Select one of the options to see what got into this LAPS product.");
+
         Label blank = new Label(controlComp, SWT.NONE);
         blank.setText("     ");
-        
+
         Button clearBtn = new Button(controlComp, SWT.PUSH);
         clearBtn.setText(" Clear ");
         clearBtn.addSelectionListener(new SelectionAdapter() {
@@ -455,7 +456,7 @@ public class LAPSToolsDlg extends CaveSWTDialog {
             }
         });
         clearBtn.setToolTipText("Clear screen.");
-        
+
         GridData gd = new GridData(SWT.FILL, SWT.DEFAULT, true, false);
         gd.widthHint = 500;
         gd.heightHint = 300;
@@ -491,12 +492,13 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         writeDomainBtn.addSelectionListener(new SelectionAdapter() {
             @Override
             public void widgetSelected(SelectionEvent e) {
-            	writeXmlfileAction();
+                writeXmlfileAction();
             }
         });
-        writeDomainBtn.setToolTipText("Write LAPS domain.xml file AND Exit.\n" +
-        		"This step will cause scripts to run that redefine the LAPS domain.\n" +
-        		"Next cycle of the analysis will show the change in domain made here." );
+        writeDomainBtn
+                .setToolTipText("Write LAPS domain.xml file AND Exit.\n"
+                        + "This step will cause scripts to run that redefine the LAPS domain.\n"
+                        + "Next cycle of the analysis will show the change in domain made here.");
     }
 
     /**
@@ -606,36 +608,31 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         nxSpnr.setEnabled(true);
         nxSpnr.setLayoutData(gd);
         nxSpnr.addListener(SWT.Verify, new Listener() {
-			@Override
-			public void handleEvent(Event event) {
-				data.setNx(nxSpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());
-			}
+            @Override
+            public void handleEvent(Event event) {
+                data.setNx(nxSpnr.getSelection());
+                areaStrLbl.setText(data.getAreaCoverageString());
+            }
         });
-        /*nxSpnr.addFocusListener(new FocusListener() {
-			@Override
-			public void focusGained(FocusEvent e) {
-				data.setNx(nxSpnr.getSelection());
-		        //areaStrLbl.setText(data.getAreaCoverageString());
-			}
-			@Override
-			public void focusLost(FocusEvent e) {
-				data.setNx(nxSpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());				
-			}
-        });
-        nxSpnr.addSelectionListener(new SelectionListener() {
-			@Override
-			public void widgetSelected(SelectionEvent e) {
-				data.setNx(nxSpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());				
-			}
-			@Override
-			public void widgetDefaultSelected(SelectionEvent e) {				
-			}
-        });
-        */
-        
+        /*
+         * nxSpnr.addFocusListener(new FocusListener() {
+         * 
+         * @Override public void focusGained(FocusEvent e) {
+         * data.setNx(nxSpnr.getSelection());
+         * //areaStrLbl.setText(data.getAreaCoverageString()); }
+         * 
+         * @Override public void focusLost(FocusEvent e) {
+         * data.setNx(nxSpnr.getSelection());
+         * areaStrLbl.setText(data.getAreaCoverageString()); } });
+         * nxSpnr.addSelectionListener(new SelectionListener() {
+         * 
+         * @Override public void widgetSelected(SelectionEvent e) {
+         * data.setNx(nxSpnr.getSelection());
+         * areaStrLbl.setText(data.getAreaCoverageString()); }
+         * 
+         * @Override public void widgetDefaultSelected(SelectionEvent e) { } });
+         */
+
         /*
          * Ny
          */
@@ -655,23 +652,21 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         nySpnr.setEnabled(true);
         nySpnr.setLayoutData(gd);
         nySpnr.addListener(SWT.Verify, new Listener() {
-			@Override
-			public void handleEvent(Event event) {
-				data.setNy(nySpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());
-			}
+            @Override
+            public void handleEvent(Event event) {
+                data.setNy(nySpnr.getSelection());
+                areaStrLbl.setText(data.getAreaCoverageString());
+            }
         });
-        /*nySpnr.addSelectionListener(new SelectionListener() {
-			@Override
-			public void widgetSelected(SelectionEvent e) {
-				data.setNy(nySpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());				
-			}
-			@Override
-			public void widgetDefaultSelected(SelectionEvent e) {				
-			}
-        });
-        */
+        /*
+         * nySpnr.addSelectionListener(new SelectionListener() {
+         * 
+         * @Override public void widgetSelected(SelectionEvent e) {
+         * data.setNy(nySpnr.getSelection());
+         * areaStrLbl.setText(data.getAreaCoverageString()); }
+         * 
+         * @Override public void widgetDefaultSelected(SelectionEvent e) { } });
+         */
         /*
          * Dx(m)
          */
@@ -691,23 +686,21 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         dxmSpnr.setLayoutData(gd);
         dxmSpnr.setMinimum(1000);
         dxmSpnr.addListener(SWT.Verify, new Listener() {
-			@Override
-			public void handleEvent(Event event) {
-				data.setGridSpacing((double) dxmSpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());
-			}
+            @Override
+            public void handleEvent(Event event) {
+                data.setGridSpacing((double) dxmSpnr.getSelection());
+                areaStrLbl.setText(data.getAreaCoverageString());
+            }
         });
-        /*dxmSpnr.addSelectionListener(new SelectionListener() {
-			@Override
-			public void widgetSelected(SelectionEvent e) {
-				data.setGridSpacing((double) dxmSpnr.getSelection());
-		        areaStrLbl.setText(data.getAreaCoverageString());				
-			}
-			@Override
-			public void widgetDefaultSelected(SelectionEvent e) {				
-			}
-        });
-*/
+        /*
+         * dxmSpnr.addSelectionListener(new SelectionListener() {
+         * 
+         * @Override public void widgetSelected(SelectionEvent e) {
+         * data.setGridSpacing((double) dxmSpnr.getSelection());
+         * areaStrLbl.setText(data.getAreaCoverageString()); }
+         * 
+         * @Override public void widgetDefaultSelected(SelectionEvent e) { } });
+         */
         /*
          * Vertical label
          */
@@ -744,7 +737,6 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         gridGroup.setLayout(new GridLayout(1, false));
         gridGroup.setLayoutData(gd);
         gridGroup.setText(" Area of Coverage ");
-        
 
         /*
          * Calculated Area label
@@ -768,7 +760,8 @@ public class LAPSToolsDlg extends CaveSWTDialog {
 
         Composite groupComp = new Composite(configureAnalysisComp, SWT.NONE);
         groupComp.setLayout(gl);
-        groupComp.setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));
+        groupComp
+                .setLayoutData(new GridData(SWT.FILL, SWT.DEFAULT, true, false));
 
         /*
          * Settings
@@ -790,8 +783,9 @@ public class LAPSToolsDlg extends CaveSWTDialog {
                 setDefaultDomain();
             }
         });
-        //defaultBtn.setToolTipText("Set to the default");
-        defaultBtn.setToolTipText("Reset all variables to values so that the LAPS domain will fully include the CWA area");
+        // defaultBtn.setToolTipText("Set to the default");
+        defaultBtn
+                .setToolTipText("Reset all variables to values so that the LAPS domain will fully include the CWA area");
 
         gd = new GridData(SWT.CENTER, SWT.DEFAULT, true, false);
         gd.widthHint = buttonWidth;
@@ -804,8 +798,8 @@ public class LAPSToolsDlg extends CaveSWTDialog {
                 resetDomain();
             }
         });
-        resetBtn.setToolTipText("Set to the values that you started with" );
-        
+        resetBtn.setToolTipText("Set to the values that you started with");
+
         /*
          * LAPS Relocator
          */
@@ -826,8 +820,8 @@ public class LAPSToolsDlg extends CaveSWTDialog {
                 loadAction();
             }
         });
-        loadBtn.setToolTipText("Load the grid info into the display." +
-        		"\nRelocate the domain by selecting and moving the grid center.");
+        loadBtn.setToolTipText("Load the grid info into the display."
+                + "\nRelocate the domain by selecting and moving the grid center.");
 
         gd = new GridData(SWT.CENTER, SWT.DEFAULT, true, false);
         gd.widthHint = buttonWidth;
@@ -841,8 +835,8 @@ public class LAPSToolsDlg extends CaveSWTDialog {
                 applyAction();
             }
         });
-        applyBtn.setToolTipText("Fill the selectors with new values, if  you" +
-        		"\nmoved the domain by relocating the center point." );
+        applyBtn.setToolTipText("Fill the selectors with new values, if  you"
+                + "\nmoved the domain by relocating the center point.");
     }
 
     /**
@@ -880,29 +874,29 @@ public class LAPSToolsDlg extends CaveSWTDialog {
 
     private void populateTypeCombo(Combo combo) {
         combo.add("-- Select a Type --");
-    	for (String choice : LapsToolsIO.getDataChoices()) {
+        for (String choice : LapsToolsIO.getDataChoices()) {
             combo.add(choice);
         }
     }
 
     private void typeAction(String type) {
         try {
-            stText.append("Begin "+type+"\n");
+            stText.append("Begin " + type + "\n");
             stText.append(LapsToolsIO.getLogs(type));
-            stText.append("End of "+type);
+            stText.append("End of " + type);
             stText.append("\n__________________________________________\n\n");
             stText.setTopIndex(stText.getLineCount());
         } catch (Exception ex) {
-            statusHandler.handle(Priority.PROBLEM,
-                    ex.getLocalizedMessage(), ex);
+            statusHandler
+                    .handle(Priority.PROBLEM, ex.getLocalizedMessage(), ex);
         }
     }
 
     private void populateSpinners() {
-        configureSpinner(cenLatSpnr, data.getGridCenter().y, 
-        		data.getValidArea().getMinY(), data.getValidArea().getMaxY());
-        configureSpinner(cenLonSpnr, data.getGridCenter().x, 
-        		data.getValidArea().getMinX(), data.getValidArea().getMaxX());
+        configureSpinner(cenLatSpnr, data.getGridCenter().y, data
+                .getValidArea().getMinY(), data.getValidArea().getMaxY());
+        configureSpinner(cenLonSpnr, data.getGridCenter().x, data
+                .getValidArea().getMinX(), data.getValidArea().getMaxX());
         configureSpinner(nxSpnr, data.getNx());
         configureSpinner(nySpnr, data.getNy());
         configureSpinner(dxmSpnr, data.getGridSpacing());
@@ -945,14 +939,16 @@ public class LAPSToolsDlg extends CaveSWTDialog {
 
     private void setDefaultDomain() {
         boolean ok = MessageDialog
-                .openConfirm(getShell(), "Confirm Exit",
+                .openConfirm(
+                        getShell(),
+                        "Confirm Exit",
                         "This will reset all variables to values so that the LAPS domain will fully includes the CWA area.");
         if (ok) {
             try {
                 LapsToolsIO.defaultDomain(data);
             } catch (Exception e) {
-                statusHandler.handle(Priority.PROBLEM,
-                        e.getLocalizedMessage(), e);
+                statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(),
+                        e);
                 e.printStackTrace();
             }
             populateSpinners();
@@ -967,23 +963,28 @@ public class LAPSToolsDlg extends CaveSWTDialog {
             try {
                 LapsToolsIO.readXmlFile(data);
             } catch (Exception e) {
-                statusHandler.handle(Priority.PROBLEM,
-                        e.getLocalizedMessage(), e);
+                statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(),
+                        e);
             }
             populateSpinners();
         }
     }
 
     private void applyAction() {
-    	if(data.getLimits()) {
-    		System.out.print("LAPS Tools Dlg: problem with domain not covering CWA");
+        if (data.getLimits()) {
+            System.out
+                    .print("LAPS Tools Dlg: problem with domain not covering CWA");
             boolean yes = MessageDialog
-            .openQuestion(getShell(), "Domain Size Error",
-                    "The size of the LAPS domain does not cover the entire CWA." +
-                    "\nWould you like to move and recenter domain?" +
-                    "\n\n(Answering 'No' will allow you to reedit text values, instead.)");
-            if(yes){ return; }
-    	}
+                    .openQuestion(
+                            getShell(),
+                            "Domain Size Error",
+                            "The size of the LAPS domain does not cover the entire CWA."
+                                    + "\nWould you like to move and recenter domain?"
+                                    + "\n\n(Answering 'No' will allow you to reedit text values, instead.)");
+            if (yes) {
+                return;
+            }
+        }
         cenLatSpnr.setEnabled(true);
         cenLonSpnr.setEnabled(true);
         nxSpnr.setEnabled(true);
@@ -1011,7 +1012,7 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         }
     }
 
-	private void loadAction() {
+    private void loadAction() {
         cenLatSpnr.setEnabled(false);
         cenLonSpnr.setEnabled(false);
         nxSpnr.setEnabled(false);
@@ -1023,7 +1024,7 @@ public class LAPSToolsDlg extends CaveSWTDialog {
         defaultBtn.setEnabled(false);
         writeDomainBtn.setEnabled(false);
         readSpinners();
-        
+
         GenericToolsResourceData rd = new GenericToolsResourceData(
                 LapsToolLayer.DEFAULT_NAME, LapsToolLayer.class);
 
@@ -1047,29 +1048,30 @@ public class LAPSToolsDlg extends CaveSWTDialog {
                 desc.getResourceList().add(rsc);
                 rsc.getCapability(EditableCapability.class).setEditable(true);
             } catch (VizException e) {
-                statusHandler.handle(Priority.PROBLEM,
-                        e.getLocalizedMessage(), e);
+                statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(),
+                        e);
             }
         }
     }
 
-	private void writeXmlfileAction() {
+    private void writeXmlfileAction() {
         if (MessageDialog.openQuestion(getShell(), "Confirmation",
                 LapsToolsIO.getWriteXmlQuestion())) {
             try {
                 LapsToolsIO.writeXmlFile(data);
-                statusHandler.handle(Priority.INFO, //SIGNIFICANT
-                        "Write EDEX domain.xml file. This action will initiated a LAPS Localization process.");
+                statusHandler
+                        .handle(Priority.INFO, // SIGNIFICANT
+                                "Write EDEX domain.xml file. This action will initiate a LAPS Localization process.");
                 close();
             } catch (Exception e) {
-                statusHandler.handle(Priority.PROBLEM,
-                        e.getLocalizedMessage(), e);
+                statusHandler.handle(Priority.PROBLEM, e.getLocalizedMessage(),
+                        e);
             }
         }
     }
-	
+
     public boolean isLapsInstalled() {
-		return isLapsInstalled;
-	}
- 
+        return isLapsInstalled;
+    }
+
 }
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 f5fb9e10f4..efd064b248 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
@@ -1689,7 +1689,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/TCMWindTool.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/procedures/TCMWindTool.py
index d268b70202..0fdd056f90 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,17 @@ 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 = {}
+
+        modelName = "Fcst"
         siteID = self.getSiteID()
         if modelName == "Fcst":
             level = "SFC"
@@ -1123,64 +1219,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 +1369,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 +1497,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 +1509,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 +1559,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 +1581,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 +1600,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,17 +1697,17 @@ 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:"]
+            
+        bgModelName = "Fcst"
         self.day3Radius = varDict["34 knot radius at 3 days (NM):"]
         self.day4Radius = varDict["34 knot radius at 4 days (NM):"]
         self.day5Radius = varDict["34 knot radius at 5 days (NM):"]
@@ -1435,29 +1719,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 +1757,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 +1772,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 +1784,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 +1844,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/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/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/textUtilities/headline/HazardsTable.py b/cave/com.raytheon.viz.gfe/localization/gfe/userPython/textUtilities/headline/HazardsTable.py
index 81d54234dd..e7c1430130 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
@@ -36,6 +36,7 @@
 #                                                 __warnETNduplication() and
 #                                                 __highestETNActiveTable.
 #    11/11/14        4953          randerso       Changed type of endTime from float to int
+#    02/05/15        4099          randerso       Fixed exception handling in __getActiveTable
 #
 
 
@@ -933,7 +934,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/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)
 
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
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/cave/com.raytheon.viz.grid/localization/menus/mrms/mrmsProducts.xml b/cave/com.raytheon.viz.grid/localization/menus/mrms/mrmsProducts.xml
index 4365054b39..31ef01f679 100644
--- a/cave/com.raytheon.viz.grid/localization/menus/mrms/mrmsProducts.xml
+++ b/cave/com.raytheon.viz.grid/localization/menus/mrms/mrmsProducts.xml
@@ -8,243 +8,243 @@
 	 
 		
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 
 	 
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 
 	 
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 			
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 		
 		
 			
 				
-				
+				
 				
 			
 				
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 		
 	 	
 			
 				
-				
+				
 				
 			
 				
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 		
 		
 			
 				
-				
+				
 				
 			
 				
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 			
 				
-				
+				
 				
 			
 		
@@ -252,97 +252,97 @@
 	 
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
 				
-				
+				
 				
 	 		
 	 		
 				
-				
+				
 				
 	 		
 	 		
 				
-				
+				
 				
 	 		
 	 		
 				
-				
+				
 				
 	 		
 	 	
 	 	
 	 		
 				
-				
+				
 				
 	 		
 		 	
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		
 		
 			
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		 	
 				
-				
+				
 				
 		 	
 		 
@@ -350,73 +350,73 @@
 	 
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 	
 			
-			
+			
 			
 	 	
 	 
-
\ No newline at end of file
+
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.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..fd9f70a287 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,7 +340,10 @@ public class SaveBestEstimate { Rwresult pRWResultNode = pRWResultHead.get(0); /* Update the elements in the RWResult node. */ - pRWResultNode.setMapxFieldType(fldtype); + + fldtype = checkAndModifyMapxFieldType(fldtype); + + pRWResultNode.setMapxFieldType(fldtype.toLowerCase()); pRWResultNode.setAutoSave(asave); pRWResultNode.setDrawPrecip(drpr); pRWResultNode.setLastSaveTime(SimulatedTime.getSystemTime() @@ -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; + } } 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/InitPrecipClimo.java b/cave/com.raytheon.viz.mpe/src/com/raytheon/viz/mpe/util/InitPrecipClimo.java index 0ae1e18bf9..d788884b20 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. * Mar 2, 2015 15660 snaples Fixed issue with if statement testing CPARM and checking for both values to be true, broken logic. * * 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 75a404f7cf..a0cbb54f85 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 @@ -43,8 +43,9 @@ import com.raytheon.viz.mpe.core.MPEDataManager; * Date Ticket# Engineer Description * ------------ ---------- ----------- -------------------------- * Feb 24, 2009 snaples Initial creation - * April , 2012 8672 lbousaidi fixed the reading of the PRISM data. - * Mar 2, 2015 15660 snaples Fixed problem with color scale using wrong values. Causing grids to be all zeros. + * April , 2012 8672 lbousaidi fixed the reading of the PRISM data. + * Feb 3, 2015 16993 snaples fixed color scale data conversion issue. + * Mar 2, 2015 15660 snaples Fixed problem with color scale using wrong values. Causing grids to be all zeros. * * * @author snaples diff --git a/cave/com.raytheon.viz.warngen/src/com/raytheon/viz/warngen/gis/Area.java b/cave/com.raytheon.viz.warngen/src/com/raytheon/viz/warngen/gis/Area.java index 47ace4c5e2..fa73aa4e3e 100644 --- a/cave/com.raytheon.viz.warngen/src/com/raytheon/viz/warngen/gis/Area.java +++ b/cave/com.raytheon.viz.warngen/src/com/raytheon/viz/warngen/gis/Area.java @@ -86,6 +86,7 @@ import com.vividsolutions.jts.geom.prep.PreparedGeometry; * Jul 22, 2014 3419 jsanchez Cleaned up converFeAreaToPartList. * Sep 14, 2014 ASM #641 dhuffman Filtered out cases where Areas do not match Zones by using * refactored WarngenLayer::filterArea. + * Mar 9, 2014 ASM #17190 D. Friedman Use fipsField and areaField for unique area ID. * * * @author chammack @@ -191,8 +192,7 @@ public class Area { } } - List uniqueFips = new ArrayList(); - List uniqueCountyname = new ArrayList(); + List uniqueAreaIDs = new ArrayList(); List areas = new ArrayList(); for (GeospatialData regionFeature : countyMap.values()) { Geometry regionGeom = regionFeature.geometry; @@ -272,14 +272,15 @@ public class Area { area.points = pointList.toArray(new String[pointList.size()]); } - String countyName = (String) regionFeature.attributes - .get("COUNTYNAME"); - if (uniqueFips.contains(area.fips) == false - || !uniqueCountyname.contains(countyName)) { - uniqueFips.add(area.fips); - if (countyName != null) { - uniqueCountyname.add(countyName); - } + /* + * Usually, the fipsField value is a unique identifier, but in some + * cases there are multiple areas that have the same value. These + * areas have different names, so we make that part of the unique + * ID. + */ + String areaID = area.fips + ':' + area.name; + if (uniqueAreaIDs.contains(areaID) == false) { + uniqueAreaIDs.add(areaID); areas.add(area); } } 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.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; } } 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', diff --git a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NOAA-161.xml b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NOAA-161.xml index 487b41a54e..5307b24ee0 100644 --- a/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NOAA-161.xml +++ b/edexOsgi/com.raytheon.edex.plugin.grib/utility/edex_static/base/grib/models/gribModels_NOAA-161.xml @@ -12,7 +12,7 @@ - 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/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 @@
- 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.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 25c2dd7e78..01d0603b71 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 332e3dd8fa..f6f2db1ca3 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 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 + 120 + 03-00:00:00 + + + MRMS_0500 120 03-00:00:00 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 bd3ee79274..b619107567 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 @@ -10,7 +10,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.7 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..607b9da1c4 --- /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("#") || line.trim().length() == 0) { + 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("#") || line.trim().length() == 0) { + 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(); + + } +} 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} 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/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 ######################################################################## 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 12ec591f8f..660aff62f3 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 @@ -cb98ce7da536d5c8a5ab8d6562c9f787734e3ea4 \ No newline at end of file +1402c774e35bd8303eb5f7e05baba4d4b732a8c4 \ 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 12ec591f8f..660aff62f3 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 @@ -cb98ce7da536d5c8a5ab8d6562c9f787734e3ea4 \ No newline at end of file +1402c774e35bd8303eb5f7e05baba4d4b732a8c4 \ No newline at end of file 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 ) ; 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 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 8b6e1dda1a..fb71ddc9ac 100644 --- a/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template +++ b/rpms/awips2.core/Installer.ldm/patch/etc/pqact.conf.template @@ -37,6 +37,7 @@ # Name changes to reflect plugin names for modelsounding, goessounding, poessounding. #20140424 3068 dgilling Add pattern for MetOp-B ASCAT T winds. #20140612 3230 rferrel Add pattern for URMA. +#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 @@ -431,6 +432,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 + +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 # TEXT ^DF.* /text/NO_STORE