001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas/
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.control;
030
031import static ucar.unidata.util.GuiUtils.hbox;
032import static ucar.unidata.util.GuiUtils.filler;
033import static ucar.unidata.util.GuiUtils.left;
034import static ucar.unidata.util.GuiUtils.topLeft;
035import static edu.wisc.ssec.mcidasv.util.CollectionHelpers.arr;
036
037import java.awt.BorderLayout;
038import java.awt.ComponentOrientation;
039import java.awt.Container;
040import java.awt.Dimension;
041import java.awt.FlowLayout;
042import java.awt.Font;
043import java.beans.PropertyChangeEvent;
044import java.io.File;
045import java.io.FileWriter;
046import java.io.IOException;
047import java.rmi.RemoteException;
048import java.text.SimpleDateFormat;
049import java.util.ArrayList;
050import java.util.Date;
051import java.util.List;
052import java.util.Scanner;
053import java.util.TimeZone;
054
055import javax.swing.*;
056
057import edu.wisc.ssec.mcidasv.McIDASV;
058import edu.wisc.ssec.mcidasv.adt.Data;
059import edu.wisc.ssec.mcidasv.adt.Env;
060import edu.wisc.ssec.mcidasv.adt.Functions;
061import edu.wisc.ssec.mcidasv.adt.History;
062import edu.wisc.ssec.mcidasv.adt.Main;
063import edu.wisc.ssec.mcidasv.adt.ReadIRImage;
064import edu.wisc.ssec.mcidasv.util.WebBrowser;
065
066import org.slf4j.Logger;
067import org.slf4j.LoggerFactory;
068
069import ucar.unidata.data.DataChoice;
070import ucar.unidata.data.DataInstance;
071import ucar.unidata.data.DataSourceImpl;
072import ucar.unidata.data.DataUtil;
073import ucar.unidata.data.grid.GridUtil;
074import ucar.unidata.data.imagery.AddeImageDataSource;
075import ucar.unidata.data.imagery.AddeImageDescriptor;
076import ucar.unidata.data.imagery.ImageDataSource;
077import ucar.unidata.geoloc.LatLonRect;
078import ucar.unidata.idv.DisplayInfo;
079import ucar.unidata.idv.control.DisplayControlImpl;
080import ucar.unidata.ui.LatLonWidget;
081import ucar.unidata.util.GuiUtils;
082import ucar.unidata.util.Misc;
083import ucar.unidata.view.geoloc.NavigatedDisplay;
084import ucar.unidata.xml.XmlObjectStore;
085import ucar.visad.Util;
086import ucar.visad.display.Animation;
087import ucar.visad.display.PointProbe;
088import ucar.visad.display.SelectorDisplayable;
089import ucar.visad.quantities.AirTemperature;
090import visad.CommonUnit;
091import visad.DateTime;
092import visad.DisplayEvent;
093import visad.FieldImpl;
094import visad.FlatField;
095import visad.Real;
096import visad.RealTuple;
097import visad.RealTupleType;
098import visad.Set;
099import visad.VisADException;
100import visad.georef.EarthLocation;
101import visad.georef.EarthLocationTuple;
102import visad.georef.LatLonPoint;
103import visad.util.DataUtility;
104import edu.wisc.ssec.mcidas.AreaDirectory;
105
106/**
107 * Advanced Dvorak Technique Display Control
108 * Algorithm developed at UW Madison/CIMSS to objectively determine tropical
109 * cyclone intensity from geostationary satellite infrared imagery.
110 * 
111 * @author Tim Olander
112 */
113
114public class ADTControl extends DisplayControlImpl {
115    
116    private static final Logger logger = LoggerFactory.getLogger(ADTControl.class);
117    
118    // Tooltip strings for the various UI buttons and inputs
119    private static final String TOOLTIP_LAND_FLAG_ON = "Apply ADT Land Interaction Rule";
120    private static final String TOOLTIP_LAND_FLAG_OFF = "Do Not Apply ADT Land Interaction Rule";
121    private static final String TOOLTIP_MANUAL = "Manually Select Storm Center In Image";
122    private static final String TOOLTIP_AUTOMATIC = "Select Forecast File For First Guess Below";
123    private static final String TOOLTIP_HISTORY = "Choose a File to Retain and Store ADT Output Data";
124    private static final String TOOLTIP_PMW = "Supplement Analysis With Passive Microwave Eye Score";
125    private static final String TOOLTIP_PENV = "Environmental Mean Sea Level Pressure";
126    private static final String TOOLTIP_34KT = "34 Knot Wind/Gale Radius";
127    private static final String TOOLTIP_MSLP_FROM_DVORAK = "Utilize Dvorak Technique to Derive MSLP";
128    private static final String TOOLTIP_MSLP_FROM_CKZ = "Utilize Coutney/Knaff/Zehr Wind Speed/Presssure Technique";
129    private static final String TOOLTIP_RMW = "Manually Input Radius of Maximum Wind";
130    private static final String TOOLTIP_RAW_T = "Manually Define Initial Value for New Storm";
131    private static final String TOOLTIP_STORM_ID = "Three Character WMO Storm Identfier";
132    private static final String TOOLTIP_SITE_ID = "Four Character Site Analysis Identifier";
133    
134    public static final String[] SCENE_TYPES = {
135        "Eye", "Pinhole Eye", "Large Eye", "CDO", "Embedded Center",
136        "Irregular CDO", "Curved Band", "Shear"
137    };
138    
139    private static final String[] FORECAST_TYPES = {
140        "ATCF", "DISC", "PACWARN", "GENERIC", "RMSC ICAO", "RMSC WTIO",
141        "TCWC AXAU", "BEST", "HURDAT"
142    };
143    
144    /** _more_ */
145    private LatLonWidget latLonWidget;
146
147    /** the probe */
148    private PointProbe probe;
149    
150    /** _more_ */
151    private LatLonPoint probeLocation;
152    
153    /** _more_ */
154    private DataChoice choice;
155    
156    /** _more_ */
157    private static boolean running = false;
158    
159    private static boolean runFullADTAnalysis = false;
160    private static boolean GUIFileOverrideTF = false;
161    
162    private static boolean GUIOverrideSceneTF;
163    
164    private static boolean GUIRunAutoTF;
165    private static boolean GUIOverrideTF;
166    private static boolean GUIATCFOutputTF;
167    private static boolean GUIInitStrengthTF;
168    private static boolean GUILandFlagTF;
169
170    // Default Java boolean value is false - need to initialize if we want true
171    private boolean GUIUseCKZTF = false;
172    private static boolean GUIVmax1or10TF = true;
173
174    private static boolean GUICommentAddTF;
175    private static boolean GUIDeleteTF;
176    private static boolean GUIATCFRecordOutputTF;
177    private static boolean GUIPMWActivateTF;
178    
179    // need to determine or provide option
180    private static int GUIDomainID;
181    
182    // need to initialize pulldown menu
183    private static int GUIForecastType = 0;
184    
185    private static int GUIMWJulianDate;
186    private static int GUIMWHHMMSSTime;
187    private static int GUIStartDate;
188    private static int GUIStartTime;
189    private static int GUIEndDate;
190    private static int GUIEndTime;
191    private static int GUIHistoryListFormat;
192    
193    private static double GUIRawTValue;
194    private static double GUIMWScore;
195    private static double GUICKZGaleRadius;
196    private static double GUICKZPenv;
197    private static double GUIRMWSize;
198    private static double GUIUserLatitude;
199    private static double GUIUserLongitude;
200    
201    private static String GUIForecastFileName;
202    private String GUIATCFStormID = null;
203    private String GUIATCFSiteID = null;
204    private static String GUIHistoryFileName;
205    private static String GUIHistoryFileListingName;
206    private static String GUICommentString;
207
208    /** _more_ */
209    private JButton adtBtn;
210    
211    private JButton forecastBtn;
212    
213    private JButton PMWFileBtn;
214    
215    private JRadioButton manButton;
216    
217    // Button to relocate probe
218    private JButton moveProbeButton;
219    
220    /** _more_ */
221    private JComboBox<String> forecastTypeBox;
222
223    private JFrame resultFrame;
224    private JTextArea resultArea;
225    private JFrame historyFrame;
226    private JTextArea historyArea;
227    
228    private JLabel selectedHistoryFile;
229    
230    private JFileChooser historyFileSaveChooser;
231    
232    private JFrame overrideSceneFrame;
233    private JLabel overrideSceneCurrentValueLabel;
234    private JComboBox<String> overrideSceneTypeBox;
235    
236    // CKZ params will need to be validated before running
237    JTextField ckzPenvTextField = null;
238    JTextField ckz34radiusTextField = null;
239    private static final String DEFAULT_PENV = "1012";
240    private static final String DEFAULT_RADIUS = "300";
241    
242    private JLabel historyLabel;
243    
244    private static String HistoryListOutput;
245    
246    private static final String SCENE_TYPE_PREFIX = "Current Scene Type: ";
247    
248    JTextField ATCFEntryStormTextField = null;
249    JTextField ATCFEntrySiteTextField = null;
250    
251    /**
252     * 
253     */
254    public ADTControl()  {
255        super();
256    }
257    
258    @Override public boolean init(DataChoice choice) throws VisADException,
259                        RemoteException {
260        logger.info("ADTControl constructor begin...");
261        
262        if (!super.init(choice)) {
263            return false;
264        }
265        this.choice = choice;
266        
267        probe = new PointProbe(new RealTuple(RealTupleType.SpatialEarth3DTuple,
268                               new double[] { 0.0, 0.0, 0.0 }));
269                               
270        probe.setVisible(false);
271        probe.setAutoSize(true);
272        probe.addPropertyChangeListener(this);
273        
274        probe.setPointSize(getDisplayScale());
275        addDisplayable(probe, FLAG_COLOR);
276        
277        // obtain initial ADT environmental parameters
278        getADTenvParameters();
279        
280        // setup window contents in Controls Window
281        setContents(setupMainWindow());
282        
283        // TJJ Jun 2017
284        // We want to initialize probe to display center if in Manual mode
285        NavigatedDisplay d = getNavigatedDisplay();
286        if (manButton.isSelected()) {
287            if (d != null) {
288                EarthLocation el = d.getCenterPoint();
289                logger.debug("Initializing probe location to: {}, {}", el.getLatitude(), el.getLongitude());
290                probeLocation = el.getLatLonPoint();
291                probe.setVisible(true);
292            }
293        }
294        updateProbeLocation();
295        return true;
296    }
297    
298    private Container setupMainWindow() {
299
300        /* add Lat/Lon position display text areas */  
301        latLonWidget = new LatLonWidget(GuiUtils.makeActionListener(this,
302                        "latLonWidgetChanged", null));
303        moveProbeButton = new JButton("Move Probe");
304        // TJJ add a strut and Probe button to the Lat-Lon widget panel
305        latLonWidget.add(Box.createHorizontalStrut(6));
306        latLonWidget.add(moveProbeButton);
307        moveProbeButton.addActionListener(ae -> {
308            // Validate the manual lat/lon text boxes 
309            String validLL = latLonWidget.isValidValues();
310            if (validLL == null) {
311                // User provided valid lat/lon data, see if it's within
312                // our display bounds. If so, move the probe
313                NavigatedDisplay d = getNavigatedDisplay();
314                if (manButton.isSelected()) {
315                    if (d != null) {
316                        EarthLocationTuple elt = null;
317                        try {
318                            elt = new EarthLocationTuple(latLonWidget.getLat(), latLonWidget.getLon(), Double.NaN);
319                            // Make sure the new Earth location is within the bounds of our satellite IR image
320                            LatLonRect bounds = d.getLatLonRect();
321                            logger.debug("Bounds min, max Lat: " + bounds.getLatMin() + ", " + bounds.getLatMax());
322                            logger.debug("Bounds min, max Lon: " + bounds.getLonMin() + ", " + bounds.getLonMax());
323                            logger.debug("ELT LatVal, LonVal: " + elt.getLatitude().getValue() + ", " + elt.getLongitude().getValue());
324                            if (bounds.contains(elt.getLatitude().getValue(), elt.getLongitude().getValue())) {
325                                probeLocation = elt.getLatLonPoint();
326                                updateProbeLocation();
327                            } else {
328                                JOptionPane.showMessageDialog(null, "Location provided is outside image bounds");
329                            }
330                        } catch (VisADException | RemoteException ve) {
331                            logException(ve);
332                        }
333                    }
334                }
335            } else {
336                JOptionPane.showMessageDialog(null, validLL);
337            }
338        });
339
340        /* add Manual or Automated storm centering buttons */
341
342        manButton = new JRadioButton("Manual");
343        manButton.setActionCommand("Manual");
344        manButton.setSelected(true);
345        manButton.setToolTipText(TOOLTIP_MANUAL);
346        JRadioButton autoButton = new JRadioButton("Automated");
347        autoButton.setActionCommand("Automated");
348        autoButton.setSelected(false);
349        autoButton.setToolTipText(TOOLTIP_AUTOMATIC);
350        ButtonGroup automangroup = new ButtonGroup();
351        automangroup.add(manButton);
352        automangroup.add(autoButton);
353        
354        /* add forecast file file selector button and file type menu */
355        JLabel autoStormSelectLabel = new JLabel("AUTOMATED STORM SELECTION");
356        JLabel manualStormSelectLabel = new JLabel("MANUAL STORM SELECTION");
357        JLabel forecastSelectLabel = new JLabel("Selected Forecast File: ");
358    
359        JLabel forecastLabel = new JLabel("No forecast file selected yet");
360        
361        manButton.addActionListener(ae -> {
362            // enable the manual lat/lon text boxes 
363            latLonWidget.getLonField().setEnabled(true);
364            latLonWidget.getLatField().setEnabled(true);
365            autoStormSelectLabel.setEnabled(false);
366            manualStormSelectLabel.setEnabled(true);
367            forecastSelectLabel.setEnabled(false);
368            moveProbeButton.setEnabled(true);
369            forecastBtn.setEnabled(false);
370            forecastTypeBox.setEnabled(false);
371            GUIRunAutoTF = false;
372        });
373        
374        autoButton.addActionListener(ae -> {
375            // disable the manual lat/lon text boxes when in auto mode
376            latLonWidget.getLonField().setEnabled(false);
377            latLonWidget.getLatField().setEnabled(false);
378            autoStormSelectLabel.setEnabled(true);
379            manualStormSelectLabel.setEnabled(false);
380            forecastSelectLabel.setEnabled(true);
381            moveProbeButton.setEnabled(false);
382            forecastBtn.setEnabled(true);
383            forecastTypeBox.setEnabled(true);
384            GUIRunAutoTF = true;
385            System.out.println("running automated ADT!!!\n");
386        });
387
388        forecastBtn = new JButton("Select Forecast File");
389        forecastBtn.setPreferredSize(new Dimension(200,30));
390        forecastBtn.addActionListener(fbtn -> {
391            GUIForecastFileName = selectForecastFile();
392            logger.trace("forecast file name={}", GUIForecastFileName);
393            forecastLabel.setText(
394               GUIForecastFileName.substring(GUIForecastFileName.lastIndexOf(File.separatorChar) + 1)
395            );
396        });
397
398        forecastTypeBox = new JComboBox<>(FORECAST_TYPES);
399        forecastTypeBox.setSelectedIndex(GUIForecastType);
400        forecastTypeBox.setPreferredSize(new Dimension(150,20));
401        forecastTypeBox.addActionListener(ame -> {
402            GUIForecastType = forecastTypeBox.getSelectedIndex();
403            logger.trace("forecast file type={}", GUIForecastType);
404        });
405        
406        forecastTypeBox.setToolTipText("Select Forecast File Type.");
407        autoStormSelectLabel.setEnabled(false);
408        forecastSelectLabel.setEnabled(false);
409        forecastBtn.setEnabled(false);
410        forecastTypeBox.setEnabled(false);
411
412        /* define default history file text field message */
413        selectedHistoryFile = new JLabel("No history file selected yet");
414
415        /* add history file selection button */
416        JButton historyBtn = new JButton("Select History File");
417        historyBtn.setToolTipText(TOOLTIP_HISTORY);
418        historyBtn.setPreferredSize(new Dimension(200, 30));
419        historyBtn.addActionListener(hbtn -> {
420            GUIHistoryFileName = selectHistoryFile();
421            logger.debug("history file name={}", GUIHistoryFileName);
422            
423            // TJJ Dec 2017 
424            // Do some cursory validation on History file before plowing ahead
425            if (! validHistoryFile(GUIHistoryFileName)) {
426                JOptionPane.showMessageDialog(null, 
427                    "Your selection does not appear to be a valid ADT History File.");
428            } else {
429                runFullADTAnalysis = true;
430                selectedHistoryFile.setText(
431                   GUIHistoryFileName.substring(GUIHistoryFileName.lastIndexOf(File.separatorChar) + 1)
432                );
433            }
434        });
435
436        /* add main ADT analysis start button */
437        adtBtn = new JButton("Run ADT Analysis");
438        adtBtn.setPreferredSize(new Dimension(250, 50));
439        adtBtn.addActionListener(ae -> runADTmain());
440        
441        /* add history file list/write button */
442        JButton listBtn = new JButton("List/Write History File");
443        listBtn.setPreferredSize(new Dimension(250, 50));
444        listBtn.addActionListener(ae -> {
445            logger.debug("listing history file name={}", GUIHistoryFileName);
446            try {
447                listHistoryFile();
448            } catch (NumberFormatException nfe) {
449                JOptionPane.showMessageDialog(null, 
450                    "Your selection does not appear to be a valid ADT History File.");
451            }
452        });
453    
454        // TJJ Jan 2017
455        // We'll keep the Manual vs. Automated PMW radio button group around
456        // in case code to support automated is added later. For now, only
457        // manual works in this version, so we'll just set the state of the
458        // buttons but not show them.
459        
460        JRadioButton PMWManButton = new JRadioButton("Manual");
461        PMWManButton.setActionCommand("Man");
462        PMWManButton.setSelected(true);
463        PMWManButton.setEnabled(true);
464        
465        JRadioButton PMWAutoButton = new JRadioButton("Automated");
466        PMWAutoButton.setActionCommand("Auto");
467        PMWAutoButton.setSelected(false);
468        PMWAutoButton.setEnabled(false);
469        
470        /* PMW Manual options */
471        JLabel pmwManDateLabel = new JLabel("Date:");
472        JLabel pmwManTimeLabel = new JLabel("Time:");
473        JLabel pmwManScoreLabel = new JLabel("Score:");
474        JTextField pmwManDateTextField = new JTextField("1900JAN01", 8);
475        pmwManDateTextField.setToolTipText("YYYYMMMDD");
476        pmwManDateTextField.addActionListener(ae -> {
477            /* read PMW overpass date */
478            JTextField src = (JTextField) ae.getSource();
479            GUIMWJulianDate =
480                Functions.cmonth2julian(src.getText());
481            GUIMWScore = -99.0;
482        });
483        JTextField pmwManTimeTextField = new JTextField("000000", 6);
484        pmwManTimeTextField.setToolTipText("HHMMSS");
485        pmwManTimeTextField.addActionListener(ae -> {
486            /* read PMW overpass time */
487            JTextField src = (JTextField) ae.getSource();
488            GUIMWHHMMSSTime = Integer.valueOf(src.getText());
489            GUIMWScore = -99.0;
490        });
491        JTextField pmwManScoreTextField = new JTextField("-99.0", 4);
492        pmwManScoreTextField.setToolTipText("Eye Score Value");
493        pmwManScoreTextField.addActionListener(ae -> {
494            /* read PMW overpass score */
495            JTextField src = (JTextField) ae.getSource();
496            GUIMWScore = Double.valueOf(src.getText());
497        });
498        pmwManDateTextField.setEnabled(false);
499        pmwManTimeTextField.setEnabled(false);
500        pmwManScoreTextField.setEnabled(false);
501        pmwManDateLabel.setEnabled(false);
502        pmwManTimeLabel.setEnabled(false);
503        pmwManScoreLabel.setEnabled(false);
504    
505        ButtonGroup pmwgroup = new ButtonGroup();
506        pmwgroup.add(PMWAutoButton);
507        pmwgroup.add(PMWManButton);
508        PMWAutoButton.addActionListener(ae -> {
509            /* enter file name */
510            // Automated - file entry
511            PMWFileBtn.setEnabled(true);
512            pmwManDateTextField.setEnabled(false);
513            pmwManTimeTextField.setEnabled(false);
514            pmwManScoreTextField.setEnabled(false);
515            pmwManDateLabel.setEnabled(false);
516            pmwManTimeLabel.setEnabled(false);
517            pmwManScoreLabel.setEnabled(false);
518        });
519        PMWManButton.addActionListener(ae -> {
520            /* enter date/time and score manually */
521            // Maunal entry
522            PMWFileBtn.setEnabled(false);
523            pmwManDateTextField.setEnabled(true);
524            pmwManTimeTextField.setEnabled(true);
525            pmwManScoreTextField.setEnabled(true);
526            pmwManDateLabel.setEnabled(true);
527            pmwManTimeLabel.setEnabled(true);
528            pmwManScoreLabel.setEnabled(true);
529        });
530        
531        /* Add PMW Analysis option buttons and entry fields */
532        JCheckBox PMWActivateButton = new JCheckBox("Activate");
533        PMWActivateButton.setActionCommand("PMW");
534        PMWActivateButton.setSelected(false);
535        PMWActivateButton.setEnabled(true);
536        PMWActivateButton.setToolTipText(TOOLTIP_PMW);
537        PMWActivateButton.addActionListener(ae -> {
538            // if on, turn off and vice versa
539            GUIPMWActivateTF = !GUIPMWActivateTF;
540            PMWManButton.setEnabled(GUIPMWActivateTF);
541            PMWManButton.setSelected(GUIPMWActivateTF);
542            pmwManDateTextField.setEnabled(GUIPMWActivateTF);
543            pmwManTimeTextField.setEnabled(GUIPMWActivateTF);
544            pmwManScoreTextField.setEnabled(GUIPMWActivateTF);
545            pmwManDateLabel.setEnabled(GUIPMWActivateTF);
546            pmwManTimeLabel.setEnabled(GUIPMWActivateTF);
547            pmwManScoreLabel.setEnabled(GUIPMWActivateTF);
548            PMWActivateButton.setSelected(GUIPMWActivateTF);
549        });
550        
551        /* add CKZ option buttons and entry fields */
552        JLabel ckzPenvLabel = new JLabel("Penv:");
553        ckzPenvLabel.setEnabled(false);
554        
555        JLabel ckz34radiusLabel = new JLabel("34kt Radius:");
556        ckz34radiusLabel.setEnabled(false);
557        
558        ckzPenvTextField = new JTextField(DEFAULT_PENV, 5);
559        ckzPenvTextField.setToolTipText(TOOLTIP_PENV);
560        ckzPenvTextField.addActionListener(ae -> {
561            JTextField src = (JTextField)ae.getSource();
562            GUICKZPenv = Integer.valueOf(src.getText());
563        });
564        ckz34radiusTextField = new JTextField(DEFAULT_RADIUS, 5);
565        ckz34radiusTextField.setToolTipText(TOOLTIP_34KT);
566        ckz34radiusTextField.addActionListener(ae -> {
567            JTextField src = (JTextField)ae.getSource();
568            GUICKZGaleRadius = Integer.valueOf(src.getText());
569        });
570        ckzPenvTextField.setEnabled(false);
571        ckz34radiusTextField.setEnabled(false);
572    
573        JRadioButton mslpDvorakButton = new JRadioButton("Dvorak");
574        mslpDvorakButton.setActionCommand("Dvorak");
575        mslpDvorakButton.setSelected(true);
576        mslpDvorakButton.setToolTipText(TOOLTIP_MSLP_FROM_DVORAK);
577        JRadioButton mslpCKZButton = new JRadioButton("CKZ");
578        mslpCKZButton.setActionCommand("CKZ");
579        mslpCKZButton.setSelected(false);
580        mslpCKZButton.setToolTipText(TOOLTIP_MSLP_FROM_CKZ);
581        ButtonGroup mslpgroup = new ButtonGroup();
582        mslpgroup.add(mslpDvorakButton);
583        mslpgroup.add(mslpCKZButton);
584        mslpDvorakButton.addActionListener(ae -> {
585            // Dvorak
586            ckzPenvTextField.setEnabled(false);
587            ckz34radiusTextField.setEnabled(false);
588            ckzPenvLabel.setEnabled(false);
589            ckz34radiusLabel.setEnabled(false);
590            mslpDvorakButton.setSelected(true);
591            mslpCKZButton.setSelected(false);
592            GUIUseCKZTF = false;
593        });
594        mslpCKZButton.addActionListener(ae -> {
595            // CKZ
596            ckzPenvTextField.setEnabled(true);
597            ckz34radiusTextField.setEnabled(true);
598            ckzPenvLabel.setEnabled(true);
599            ckz34radiusLabel.setEnabled(true);
600            mslpDvorakButton.setSelected(false);
601            mslpCKZButton.setSelected(true);
602            GUIUseCKZTF = true;
603        });
604        
605        /* various other keyword options */
606        /* Initial classification entry -- RAWT */
607        JLabel RawTLabel = new JLabel("Raw T:");
608        JTextField RawTTextField = new JTextField("1.0", 4);
609        RawTTextField.setToolTipText(TOOLTIP_RAW_T);
610        RawTTextField.addActionListener(ae -> {
611            JTextField src = (JTextField)ae.getSource();
612            GUIRawTValue = Double.valueOf(src.getText());
613            GUIInitStrengthTF = GUIRawTValue >= 1.0;
614        });
615        
616        /* Radius of Max Wind entry -- RMW */
617        JLabel RMWLabel = new JLabel("RMW:");
618        JTextField RMWTextField = new JTextField("-99", 4);
619        RMWTextField.setToolTipText(TOOLTIP_RMW);
620        RMWTextField.addActionListener(ae -> {
621            JTextField src = (JTextField)ae.getSource();
622            GUIRMWSize = Double.valueOf(src.getText());
623        });
624        
625        /* Override option */
626        JButton sceneOverrideButton = new JButton("Override Scene Type");
627        JLabel OverrideLabel = new JLabel(SCENE_TYPE_PREFIX + SCENE_TYPES[Env.OverrideSceneTypeIndex]);
628        sceneOverrideButton.addActionListener(ae -> {
629            overrideSceneFrame.setVisible(true);
630        });
631        
632        /* ATCF Analysis Output Checkbox */
633        
634        JLabel ATCFOutputLabel = new JLabel("ATCF Output:");
635        JCheckBox ATCFOutputButton = new JCheckBox("Activate");
636        ATCFOutputButton.setActionCommand("ATCF");
637        ATCFOutputButton.setSelected(false);
638        ATCFOutputButton.setEnabled(true);
639    
640        JLabel ATCFEntryStormLabel = new JLabel("Storm ID:");
641        ATCFEntryStormTextField = new JTextField("XXX", 8);
642        ATCFEntryStormTextField.setToolTipText(TOOLTIP_STORM_ID);
643        JLabel ATCFEntrySiteLabel = new JLabel("Site ID:");
644        ATCFEntrySiteTextField = new JTextField("XXXX", 8);
645        ATCFEntrySiteTextField.setToolTipText(TOOLTIP_SITE_ID);
646        ATCFEntryStormLabel.setEnabled(false);
647        ATCFEntryStormTextField.setEnabled(false);
648        ATCFEntrySiteLabel.setEnabled(false);
649        ATCFEntrySiteTextField.setEnabled(false);
650        ATCFEntryStormTextField.addActionListener(ae -> {
651            JTextField src = (JTextField)ae.getSource();
652            GUIATCFStormID = src.getText();
653        });
654        ATCFEntrySiteTextField.addActionListener(ae -> {
655            JTextField src = (JTextField)ae.getSource();
656            GUIATCFSiteID = src.getText();
657        });
658        
659        ATCFOutputButton.addActionListener(ae -> {
660            // if on, turn off and vice versa
661            GUIATCFRecordOutputTF = !GUIATCFRecordOutputTF;
662            ATCFEntryStormLabel.setEnabled(GUIATCFRecordOutputTF);
663            ATCFEntryStormTextField.setEnabled(GUIATCFRecordOutputTF);
664            ATCFEntrySiteLabel.setEnabled(GUIATCFRecordOutputTF);
665            ATCFEntrySiteTextField.setEnabled(GUIATCFRecordOutputTF);
666            ATCFOutputButton.setSelected(GUIATCFRecordOutputTF);
667        });
668        
669        /* Land Flag button -- LAND */
670        JLabel LandFlagLabel = new JLabel("Land Flag:");
671        JRadioButton LandONButton = new JRadioButton("ON");
672        LandONButton.setActionCommand("On");
673        LandONButton.setSelected(true);
674        LandONButton.setToolTipText(TOOLTIP_LAND_FLAG_ON);
675        JRadioButton LandOFFButton = new JRadioButton("OFF");
676        LandOFFButton.setActionCommand("Off");
677        LandOFFButton.setSelected(false);
678        LandOFFButton.setToolTipText(TOOLTIP_LAND_FLAG_OFF);
679        ButtonGroup landgroup = new ButtonGroup();
680        landgroup.add(LandONButton);
681        landgroup.add(LandOFFButton);
682        LandONButton.addActionListener(ae -> {
683            // LAND=YES
684            LandONButton.setSelected(true);
685            LandOFFButton.setSelected(false);
686            GUILandFlagTF = true;
687        });
688        LandOFFButton.addActionListener(ae -> {
689            // LAND=NO
690            LandONButton.setSelected(false);
691            LandOFFButton.setSelected(true);
692            GUILandFlagTF = false;
693        });
694        
695        /*  Wind Speed Vmax output button -- VOUT */
696        JLabel VOutLabel = new JLabel("VMax:");
697        JRadioButton V1MinButton = new JRadioButton("One-minute");
698        V1MinButton.setActionCommand("One");
699        V1MinButton.setSelected(true);
700        V1MinButton.setToolTipText("Maximum Wind Speed Averaged Over");
701        JRadioButton V10MinButton = new JRadioButton("Ten-minute");
702        V10MinButton.setActionCommand("Ten");
703        V10MinButton.setSelected(false);
704        V10MinButton.setToolTipText("Maximum Wind Speed Averaged Over");
705        ButtonGroup voutgroup = new ButtonGroup();
706        voutgroup.add(V1MinButton);
707        voutgroup.add(V10MinButton);
708        V1MinButton.addActionListener(ae -> {
709            // 1-minute winds
710            V1MinButton.setSelected(true);
711            V10MinButton.setSelected(false);
712            GUIVmax1or10TF = true;
713        });
714        V10MinButton.addActionListener(ae -> {
715            // 10-minute winds
716            V1MinButton.setSelected(false);
717            V10MinButton.setSelected(true);
718            GUIVmax1or10TF = false;
719        });
720        
721        JLabel blankfield = new JLabel("");
722        
723        // TJJ Jan 2018 - interim link to Help for McV 1.7 release 
724        JButton helpLinkLabel = new JButton("<html><a href=\"https://www.ssec.wisc.edu\">Help</a></html>");
725        helpLinkLabel.setToolTipText("Opens ADT Help PDF in your system web browser");
726        helpLinkLabel.addActionListener(e -> WebBrowser.browse("https://www.ssec.wisc.edu/mcidas/software/v/resources/adt/McV_ADT_1p7.pdf"));
727
728        GuiUtils.tmpInsets = GuiUtils.INSETS_5;
729        JComponent widgets =
730            GuiUtils.formLayout(
731                arr(left(hbox(arr(new JLabel("Storm Center Selection:"), manButton, autoButton), 5)),
732                    filler(),
733                    left(hbox(arr(manualStormSelectLabel), 10)),
734                    filler(),
735                    left(hbox(arr(filler(30, 1), latLonWidget))), filler(),
736                    left(hbox(arr(autoStormSelectLabel), 10)), filler(),
737                    left(hbox(arr(filler(30, 1), forecastBtn, forecastTypeBox,
738                        forecastSelectLabel, forecastLabel), 5)), filler(),
739                    left(hbox(arr(blankfield))),
740                    filler(1, 5),
741                    left(hbox(arr(new JLabel("HISTORY FILE INFORMATION")), 10)), filler(),
742                    left(hbox(arr(filler(30, 1), historyBtn, new JLabel
743                        ("Selected History File: "), selectedHistoryFile), 5)),
744                    filler(),
745                    left(hbox(arr(blankfield))),
746                    filler(1, 5),
747                    left(hbox(arr(new JLabel("PMW ANALYSIS")), 10)), filler(),
748                    left(hbox(arr(filler(30, 1), PMWActivateButton,
749                        pmwManDateLabel, pmwManDateTextField, pmwManTimeLabel,
750                        pmwManTimeTextField, pmwManScoreLabel, pmwManScoreTextField), 5)), filler(),
751                    left(hbox(arr(blankfield))),
752                    filler(1, 5),
753                    left(hbox(arr(new JLabel("MISCELLANEOUS OPTIONS")), 10)), filler(),
754                    left(hbox(arr(filler(30, 1), new JLabel("MSLP Conversion Method:"), mslpDvorakButton, mslpCKZButton, ckzPenvLabel, ckzPenvTextField, ckz34radiusLabel, ckz34radiusTextField), 5)), filler(),
755                    left(hbox(arr(filler(30, 1), sceneOverrideButton, OverrideLabel), 5)), filler(),
756                    left(hbox(arr(filler(30, 1), LandFlagLabel, LandONButton, LandOFFButton, filler(20, 1), VOutLabel, V1MinButton, V10MinButton, filler(20, 1), RawTLabel, RawTTextField, RMWLabel, RMWTextField), 5)), filler(),
757                    left(hbox(arr(filler(30, 1), ATCFOutputLabel, ATCFOutputButton, ATCFEntryStormLabel, ATCFEntryStormTextField, ATCFEntrySiteLabel, ATCFEntrySiteTextField), 5)), filler(),
758                    left(hbox(arr(filler(80, 1), adtBtn, listBtn, helpLinkLabel), 20)), filler()));
759                    
760        JPanel controls = topLeft(widgets);
761
762        /* set up ADT Bulletin display area */
763        resultArea = new JTextArea();
764        resultArea.setEditable(false);
765
766        Font c = new Font("Courier", Font.BOLD, 12);
767        
768        resultFrame = new JFrame("ADT Results");
769        resultFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
770        JScrollPane resultScroller = new JScrollPane(resultArea);
771        resultScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
772        resultFrame.add(resultScroller, BorderLayout.CENTER);
773        resultFrame.setPreferredSize(new Dimension(400, 600));
774        resultFrame.setFont(c);
775
776        /* set up ADT History File display area */
777        historyFrame = new JFrame("ADT History File Listing");
778        Container historyContainer = historyFrame.getContentPane();
779        historyFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
780        JPanel historyTextPanel = new JPanel();
781        FlowLayout historyListLayout = new FlowLayout();
782        historyTextPanel.setLayout(historyListLayout);
783        historyListLayout.setAlignment(FlowLayout.CENTER);
784           
785        historyArea = new JTextArea(50,150);
786        historyArea.setEditable(false);
787        JScrollPane historyScroller = new JScrollPane(historyArea);
788        historyScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
789        historyScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
790        historyScroller.setPreferredSize(new Dimension(1200, 400));
791        historyArea.setFont(c);
792
793        JPanel historyLabelPanel = new JPanel();
794        FlowLayout HistoryLabelLayout = new FlowLayout();
795        historyLabelPanel.setLayout(HistoryLabelLayout);
796        HistoryLabelLayout.setAlignment(FlowLayout.CENTER);
797        historyLabel = new JLabel("No History File Selected");
798        historyLabel.setPreferredSize(new Dimension(800, 20));
799        historyLabel.setFont(c);
800        
801        /* history file Editing Date Selection window */
802        JFrame historyDateFrame = new JFrame("History File Editor");
803        Container historyDateContainer = historyDateFrame.getContentPane();
804        historyDateFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
805        JPanel historyDatePanel = new JPanel();
806        FlowLayout DateStartEndLayout = new FlowLayout();
807        historyDatePanel.setLayout(DateStartEndLayout);
808        DateStartEndLayout.setAlignment(FlowLayout.CENTER);
809        JLabel historyDateStartLabel = new JLabel("Start:");
810        JLabel historyDateStartDateLabel = new JLabel("Date");
811        JTextField historyDateStartDateTextField = new JTextField("0000XXX00", 10);
812        JLabel historyDateStartTimeLabel = new JLabel("Time");
813        JTextField historyDateStartTimeTextField = new JTextField("-1", 8);
814        JLabel historyDateEndLabel = new JLabel("End");
815        JLabel historyDateEndDateLabel = new JLabel("Date");
816        JTextField historyDateEndDateTextField = new JTextField("0000XXX00", 10);
817        JLabel historyDateEndTimeLabel = new JLabel("Time");
818        JTextField historyDateEndTimeTextField = new JTextField("-1", 8);
819        
820        JPanel historyButtonPanel = new JPanel();
821        FlowLayout HistoryButtonLayout = new FlowLayout();
822        historyButtonPanel.setLayout(HistoryButtonLayout);
823        HistoryButtonLayout.setAlignment(FlowLayout.CENTER);
824
825        JButton historySaveListingBtn = new JButton("Write History");
826        historySaveListingBtn.setPreferredSize(new Dimension(200, 20));
827        historySaveListingBtn.addActionListener(ae -> {
828            GUIHistoryFileListingName = selectHistoryFileOutput();
829            logger.debug("saving history listing file name={}", GUIHistoryFileListingName);
830            GUIHistoryListFormat = -1;
831        });
832        JButton historyWriteATCFBtn = new JButton("Write ATCF");
833        historyWriteATCFBtn.setPreferredSize(new Dimension(200, 20));
834        historyWriteATCFBtn.addActionListener(ae -> {
835            GUIATCFOutputTF = true;
836            GUIHistoryListFormat = 0;
837            logger.debug("calling ATCFFileOutput");
838            ATCFFileOutput(0);
839        });
840        historyLabelPanel.add(historyLabel);
841        historyLabelPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
842        historyTextPanel.add(historyScroller);
843        historyTextPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
844
845        historyButtonPanel.add(historySaveListingBtn);
846        historyButtonPanel.add(historyWriteATCFBtn);
847        historyButtonPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
848        historyContainer.add(historyLabelPanel,BorderLayout.NORTH);
849        historyContainer.add(historyTextPanel,BorderLayout.CENTER);
850        historyContainer.add(historyButtonPanel,BorderLayout.SOUTH);
851        
852        historyDateStartDateTextField.addActionListener(ae -> {
853            JTextField textField = (JTextField)ae.getSource();
854            GUIStartDate = Functions.cmonth2julian(textField.getText());
855        });
856        historyDateStartTimeTextField.addActionListener(ae -> {
857            JTextField textField = (JTextField)ae.getSource();
858            GUIStartTime = Integer.valueOf(textField.getText());
859        });
860        historyDateEndDateTextField.addActionListener(ae -> {
861            JTextField textField = (JTextField)ae.getSource();
862            GUIEndDate = Functions.cmonth2julian(textField.getText());
863        });
864        historyDateEndTimeTextField.addActionListener(ae -> {
865            JTextField textField = (JTextField)ae.getSource();
866            GUIEndTime = Integer.valueOf(textField.getText());
867        });
868        
869        JPanel historyDateButtonPanel = new JPanel();
870        FlowLayout DateButtonLayout = new FlowLayout();
871        historyDateButtonPanel.setLayout(DateButtonLayout);
872        DateButtonLayout.setAlignment(FlowLayout.CENTER);
873        JRadioButton historyEditDeleteButton = new JRadioButton("Delete Records");
874        historyEditDeleteButton.setActionCommand("Delete");
875        historyEditDeleteButton.setSelected(false);
876        JRadioButton historyEditAddCommentButton = new JRadioButton("Add Comment");
877        historyEditAddCommentButton.setActionCommand("Comment");
878        historyEditAddCommentButton.setSelected(false);
879        ButtonGroup editgroup = new ButtonGroup();
880        editgroup.add(historyEditDeleteButton);
881        editgroup.add(historyEditAddCommentButton);
882        JLabel historyEditAddCommentLabel = new JLabel("Comment:");
883        JTextField historyEditAddCommentTextField = new JTextField("no comment entered", 25);
884        historyEditAddCommentTextField.setEnabled(false);
885        
886        historyEditDeleteButton.addActionListener(ae -> {
887            // history Edit - Delete
888            historyEditDeleteButton.setSelected(true);
889            historyEditAddCommentButton.setSelected(false);
890            historyEditAddCommentLabel.setEnabled(false);
891            historyEditAddCommentTextField.setEnabled(false);
892            GUICommentAddTF = false;
893            GUIDeleteTF = true;
894        });
895        
896        historyEditAddCommentButton.addActionListener(ae -> {
897            // history Edit - Add Comment
898            historyEditDeleteButton.setSelected(false);
899            historyEditAddCommentButton.setSelected(true);
900            historyEditAddCommentLabel.setEnabled(true);
901            historyEditAddCommentTextField.setEnabled(true);
902            GUICommentAddTF = true;
903            GUIDeleteTF = false;
904        });
905        historyEditAddCommentTextField.addActionListener(ae -> {
906            JTextField src = (JTextField)ae.getSource();
907            GUICommentString = src.getText();
908        });
909        JPanel historyEditInputPanel = new JPanel();
910        FlowLayout EditInputButtonLayout = new FlowLayout();
911        historyEditInputPanel.setLayout(EditInputButtonLayout);
912        EditInputButtonLayout.setAlignment(FlowLayout.CENTER);
913        JButton historyEditApplyButton = new JButton("Apply Edits");
914        historyEditApplyButton.setPreferredSize(new Dimension(150, 20));
915        historyEditApplyButton.addActionListener(ae -> modifyHistoryFile());
916        JButton historyEditCancelButton = new JButton("Cancel");
917        historyEditCancelButton.setPreferredSize(new Dimension(150, 20));
918        historyEditCancelButton.addActionListener(ae -> historyDateFrame.dispose());
919        historyDatePanel.add(historyDateStartLabel);
920        historyDatePanel.add(historyDateStartDateLabel);
921        historyDatePanel.add(historyDateStartDateTextField);
922        historyDatePanel.add(historyDateStartTimeLabel);
923        historyDatePanel.add(historyDateStartTimeTextField);
924        historyDatePanel.add(historyDateEndLabel);
925        historyDatePanel.add(historyDateEndDateLabel);
926        historyDatePanel.add(historyDateEndDateTextField);
927        historyDatePanel.add(historyDateEndTimeLabel);
928        historyDatePanel.add(historyDateEndTimeTextField);
929        historyDatePanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
930        historyDateButtonPanel.add(historyEditDeleteButton);
931        historyDateButtonPanel.add(historyEditAddCommentButton);
932        historyDateButtonPanel.add(historyEditAddCommentLabel);
933        historyDateButtonPanel.add(historyEditAddCommentTextField);
934        historyEditInputPanel.add(historyEditApplyButton);
935        historyEditInputPanel.add(historyEditCancelButton);
936        historyDateContainer.add(historyDatePanel, BorderLayout.NORTH);
937        historyDateContainer.add(historyDateButtonPanel, BorderLayout.CENTER);
938        historyDateContainer.add(historyEditInputPanel, BorderLayout.SOUTH);
939
940        /* set up Scene Type Override Window display window */
941        overrideSceneFrame = new JFrame("Override Scene Type");
942        overrideSceneFrame.setSize(new Dimension(400, 300));
943        Container overrideSceneContainer = overrideSceneFrame.getContentPane();
944        overrideSceneFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
945        JPanel overrideSceneCurrentPanel = new JPanel();
946        FlowLayout OverrideSceneCurrentLayout = new FlowLayout();
947        overrideSceneCurrentPanel.setLayout(OverrideSceneCurrentLayout);
948        OverrideSceneCurrentLayout.setAlignment(FlowLayout.CENTER);
949        JLabel overrideSceneCurrentLabel = new JLabel("Current Scene Type:");
950        overrideSceneCurrentValueLabel = new JLabel(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
951        JPanel overrideSceneSelectPanel = new JPanel();
952        FlowLayout OverrideSceneSelectLayout = new FlowLayout();
953        overrideSceneCurrentPanel.setLayout(OverrideSceneSelectLayout);
954        OverrideSceneSelectLayout.setAlignment(FlowLayout.CENTER);
955        JLabel overrideSceneSelectLabel = new JLabel("Select New Scene Type:");
956        overrideSceneTypeBox = new JComboBox<>(SCENE_TYPES);
957        overrideSceneTypeBox.setSelectedIndex(Env.OverrideSceneTypeIndex);
958        overrideSceneTypeBox.setPreferredSize(new Dimension(150, 20));
959        // overrideSceneTypeBox.addActionListener(ame -> Env.OverrideSceneTypeIndex = overrideSceneTypeBox.getSelectedIndex());
960        JPanel overrideSceneButtonPanel = new JPanel();
961        FlowLayout OverrideSceneButtonLayout = new FlowLayout();
962        overrideSceneButtonPanel.setLayout(OverrideSceneButtonLayout);
963        OverrideSceneButtonLayout.setAlignment(FlowLayout.CENTER);
964        JButton overrideSceneAcceptButton = new JButton("Accept New Scene");
965        overrideSceneAcceptButton.setPreferredSize(new Dimension(190, 20));
966        overrideSceneAcceptButton.addActionListener(ae -> {
967            // accept new scene selection
968            overrideSceneFrame.setVisible(false);
969            Env.OverrideSceneTypeIndex = overrideSceneTypeBox.getSelectedIndex();
970            OverrideLabel.setText(SCENE_TYPE_PREFIX + SCENE_TYPES[Env.OverrideSceneTypeIndex]);
971            overrideSceneCurrentValueLabel.setText(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
972            // runADTmain();
973        });
974        JButton overrideSceneCancelButton = new JButton("Keep Current Scene");
975        overrideSceneCancelButton.setPreferredSize(new Dimension(190, 20));
976        overrideSceneCancelButton.addActionListener(ae -> {
977            overrideSceneFrame.setVisible(false);
978            // runADTmain();
979        });
980        overrideSceneCurrentPanel.add(overrideSceneCurrentLabel);
981        overrideSceneCurrentPanel.add(overrideSceneCurrentValueLabel);
982        overrideSceneSelectPanel.add(overrideSceneSelectLabel);
983        overrideSceneSelectPanel.add(overrideSceneTypeBox);
984        overrideSceneButtonPanel.add(overrideSceneAcceptButton);
985        overrideSceneButtonPanel.add(overrideSceneCancelButton);
986        overrideSceneContainer.add(overrideSceneCurrentPanel, BorderLayout.NORTH);
987        overrideSceneContainer.add(overrideSceneSelectPanel, BorderLayout.CENTER);
988        overrideSceneContainer.add(overrideSceneButtonPanel, BorderLayout.SOUTH);
989
990        JScrollPane scrollPane = new JScrollPane(controls);
991        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
992        return scrollPane;
993    }
994
995    /**
996     * Do some cursory checking on validity of selected History file
997     * @param historyFileName
998     * @return true is seems ok
999     */
1000    
1001    private boolean validHistoryFile(String historyFileName) {
1002        boolean seemsOk = true;
1003        
1004        History CurrentHistory = new History();
1005        
1006        try {
1007            logger.debug("trying to read history file {}", historyFileName);
1008            CurrentHistory.ReadHistoryFile(historyFileName);
1009        } catch (IOException exception) {
1010            logger.warn("History file %s is not valid", historyFileName);
1011            seemsOk = false;
1012        }
1013        
1014        logger.debug("Number of history records: {}", History.HistoryNumberOfRecords());
1015        if (History.HistoryNumberOfRecords() == 0) seemsOk = false;
1016        return seemsOk;
1017    }
1018
1019    @Override
1020    protected void getHelpMenuItems(List items, boolean forMenuBar) {
1021        items.add(GuiUtils.makeMenuItem("Details", this, "showDetails"));
1022        JMenuItem jmi = new JMenuItem("User's Guide");
1023        GuiUtils.setIcon(jmi, "/auxdata/ui/icons/help.png");
1024        jmi.addActionListener(e -> {
1025            WebBrowser.browse("https://www.ssec.wisc.edu/mcidas/software/v/resources/adt/McV_ADT_1p7.pdf");
1026        });
1027        items.add(jmi);
1028    }
1029
1030    private void runADTmain() {
1031        if (!running) {
1032            running = true;
1033            adtBtn.setEnabled(false);
1034            adtBtn.setText("Running");
1035            Misc.run(() -> {
1036                runADT();
1037                ExitADT();
1038            });
1039        }
1040    }
1041        
1042    private void runADT() {
1043        Main StormADT = new Main();
1044        String ADTRunOutput;
1045        String ErrorMessage;
1046        
1047        if (GUIFileOverrideTF) {
1048            String GUIOverrideFilePath = System.getenv("ODTHOME");
1049            if (GUIOverrideFilePath == null) {
1050                GUIOverrideFilePath = System.getenv("HOME");
1051            }
1052            String GUIOverrideFile = GUIOverrideFilePath + "/runadt.nogui.inputs.txt";
1053            /* GUIFileOverrideCheckBoxToggle();  change toggle back to OFF */
1054            int RetVal = ReadGUIOverrideInputFile(GUIOverrideFile);
1055            if (RetVal == -1) {
1056                ErrorMessage = String.format("Error reading GUI override file %s\n",GUIOverrideFile);
1057                System.out.println(ErrorMessage);
1058                userMessage(ErrorMessage);
1059                ExitADT();
1060                return;
1061            }
1062        }
1063        
1064        loadADTenvParameters();
1065        
1066        boolean RunAuto = Env.AutoTF;
1067        
1068        // In auto mode, make sure a valid forecast file was selected
1069        if (RunAuto) {
1070            if (GUIForecastFileName == null) {
1071                userMessage("A valid forecast file must be selected to use Automated mode.");
1072                ExitADT();
1073                return;
1074            }
1075        }
1076
1077        /* set storm position either through automated storm selection or by manual choice */
1078        GetImageDateTime();
1079        int ReturnVal = StormADT.GetInitialPosition();  // should set up to throw exception instead of return value
1080        if (ReturnVal < 0) {
1081            ErrorMessage = "Error obtaining initial position... exiting ADT\n";
1082            System.out.println(ErrorMessage);
1083            userMessage(ErrorMessage);
1084            ExitADT();
1085        } else {
1086            if (RunAuto) {
1087                try {
1088                    float CenterLatitude = (float)Env.SelectedLatitude;
1089                    float CenterLongitude =  (float)Env.SelectedLongitude;
1090                    /* System.out.println("pre-ARCHER latitude=%f longitude=%f\n",CenterLatitude,CenterLongitude); */
1091                    GetImageData(CenterLatitude, CenterLongitude);
1092                } catch (Exception exception) {
1093                    ErrorMessage = "Error reading IR data pre-ARCHER\n";
1094                    System.out.println(ErrorMessage);
1095                    userMessage(ErrorMessage);
1096                    ExitADT();
1097                    return;
1098                }
1099                StormADT.GetARCHERPosition();
1100            } else {
1101                if (probeLocation == null) {
1102                    ErrorMessage = "Please select storm center location manually and try again";
1103                    System.out.println(ErrorMessage);
1104                    userMessage(ErrorMessage);
1105                    ExitADT();
1106                    return;
1107                } else {
1108                    Env.SelectedLatitude = probeLocation.getLatitude().getValue();
1109                    Env.SelectedLongitude = probeLocation.getLongitude().getValue();
1110                }
1111            }
1112            
1113            try {
1114                float CenterLatitude = (float) Env.SelectedLatitude;
1115                float CenterLongitude =  (float) Env.SelectedLongitude;
1116                /* System.out.println("latitude=%f longitude=%f domain=%d\n",CenterLatitude,CenterLongitude,DomainID); */
1117                GetImageData(CenterLatitude, CenterLongitude);
1118            } catch (Exception e) {
1119                ErrorMessage = "Error reading IR data in getimagedata()\n";
1120                logger.error(ErrorMessage.trim(), e);
1121                userMessage(ErrorMessage);
1122                ExitADT();
1123                return;
1124            }
1125            
1126            // TJJ Jun 2017 Just about ready, a few more validation checks and we can run          
1127            // If CKZ chosen as MSLP Conversion Method, need to validate Penv and 34kt Radius fields
1128            // This may not be the best place to do this, but it's better than not doing it ;-)
1129            
1130            if (GUIUseCKZTF) {
1131                
1132                String newPenvStr = ckzPenvTextField.getText();
1133                boolean badPenv = false;
1134                try {
1135                    int newPenv = Integer.valueOf(newPenvStr);
1136                    if (newPenv > 0) {
1137                        GUICKZPenv = newPenv;
1138                        Env.CKZPenv = GUICKZPenv;
1139                    } else {
1140                        badPenv = true;
1141                    }
1142                } catch (NumberFormatException nfe) {
1143                    badPenv = true;
1144                }
1145                
1146                if (badPenv) {
1147                    // Throw up a warning and bail out
1148                    showBadIntWarning("Penv", newPenvStr);
1149                    return;
1150                }
1151                
1152                String newRadiusStr = ckz34radiusTextField.getText();
1153                boolean badNewRadius = false;
1154                try {
1155                    int newRadius = Integer.valueOf(newRadiusStr);
1156                    if (newRadius > 0) {
1157                        GUICKZGaleRadius = newRadius;
1158                        Env.CKZGaleRadius = GUICKZGaleRadius;
1159                    } else {
1160                        badNewRadius = true;
1161                    }
1162                } catch (NumberFormatException nfe) {
1163                    badNewRadius = true;
1164                }
1165                
1166                if (badNewRadius) {
1167                    // Throw up a warning and bail out
1168                    showBadIntWarning("Radius", newRadiusStr);
1169                    return;
1170                }
1171                
1172            }
1173             
1174            try {
1175                logger.debug("RUNNING ADT ANALYSIS");
1176                ADTRunOutput = StormADT.RunADTAnalysis(runFullADTAnalysis,GUIHistoryFileName);
1177            } catch (IOException exception) {
1178                ErrorMessage = "Error with call to StormADT.RunADT()\n";
1179                logger.error(ErrorMessage.trim(), exception);
1180                userMessage(ErrorMessage);
1181                ExitADT();
1182                return;
1183            }
1184            if (GUIOverrideSceneTF) {
1185                /* System.out.println("Overriding scene type!!!  Scene value=%d\n",InitialSceneTypeValue); */
1186                overrideSceneCurrentValueLabel.setText(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
1187                overrideSceneFrame.pack();
1188                overrideSceneFrame.setVisible(true);
1189                ExitADT();
1190            } else {
1191                logger.debug("done running ADT");
1192    
1193                resultArea.setText(ADTRunOutput);
1194                resultFrame.pack();
1195                resultFrame.setVisible(true);
1196 
1197                // TJJ Dec 2017
1198                // This is in reference to Request #11, Bug #17 from
1199                // http://mcidas.ssec.wisc.edu/inquiry-v/?inquiry=1187
1200                // Since the intent here is to modify the currently active history file by appending
1201                // one record, and since that record insert had been previously commented out below,
1202                // we'll assume this was never working properly in the first place.  To prevent the
1203                // current History File from being clobbered, we just won't do the re-write for now,
1204                // since as is, a deep Exception zeros out the file, and the original file should 
1205                // at the very least remain unmodified.
1206                
1207//                if (GUIHistoryFileName != null) {
1208//                    try {
1209//                        // int[] InsertRecs = History.InsertHistoryRecord(runFullADTAnalysis,GUIHistoryFileName);
1210//                        /* System.out.println("*** Modified=%d InsertOverwriteFlag=%d***\n",InsertRecs[0],InsertRecs[1]); */
1211//                        int NumRecs = History.WriteHistoryFile(GUIHistoryFileName);
1212//                        ErrorMessage = String.format("Number of records written to history file: %d\n", NumRecs);
1213//                    } catch (IOException exception) {
1214//                        ErrorMessage = String.format("Error writing history file %s\n", GUIHistoryFileName);
1215//                    } catch (Exception e) {
1216//                        logger.error("Exception: ", e);
1217//                        ErrorMessage = String.format("Error writing history file %s\n", GUIHistoryFileName);
1218//                    }
1219//                    logger.warn(ErrorMessage.trim());
1220//                    userMessage(ErrorMessage);
1221//                }
1222                
1223                if (GUIATCFRecordOutputTF) {
1224                    ATCFFileOutput(-1);
1225                }
1226                
1227                ExitADT();
1228            }
1229        }
1230    }
1231    
1232    /**
1233     * Show a warning about a certain parameter needing to be greater than zero.
1234     * 
1235     * @param type Parameter name. Cannot be {@code null}.
1236     * @param badValue Erroneous value. Cannot be {@code null}.
1237     */
1238    private void showBadIntWarning(String type, String badValue) {
1239        String msg = "Invalid %s value: %s\nPlease provide a positive integer.";
1240        JOptionPane.showMessageDialog(null, 
1241                                      String.format(msg, type, badValue));
1242        ExitADT();
1243    }
1244    
1245    private void ExitADT() {
1246        running = false;
1247        adtBtn.setEnabled(true);
1248        adtBtn.setText("Run Analysis");
1249    }
1250    
1251    /*
1252     * Override for additional local cleanup
1253     * (non-Javadoc)
1254     * @see ucar.unidata.idv.control.DisplayControlImpl#doRemove()
1255     */
1256
1257    @Override public void doRemove() throws RemoteException, VisADException {
1258        super.doRemove();
1259        if (resultFrame != null) {
1260            resultFrame.dispose();
1261        }
1262        if (historyFrame != null) {
1263            historyFrame.dispose();
1264        }
1265    }
1266
1267    private void listHistoryFile() {
1268        HistoryListOutput = null;
1269        
1270        History CurrentHistory = new History();
1271        
1272        // Make sure a valid History File has been selected. At startup, value will be null
1273        if (GUIHistoryFileName == null) {
1274            JOptionPane.showMessageDialog(null, 
1275            "Please first select a valid ADT History File.");
1276            return;
1277        }
1278        
1279        try {
1280            logger.debug("trying to read history file {}", GUIHistoryFileName);
1281            CurrentHistory.ReadHistoryFile(GUIHistoryFileName);
1282        } catch (IOException exception) {
1283            String ErrorMessage = String.format("History file %s is not found",GUIHistoryFileName);
1284            logger.warn(ErrorMessage);
1285            userMessage(ErrorMessage);
1286            return;
1287        }
1288        
1289        logger.debug("Number of history records: {}", History.HistoryNumberOfRecords());
1290        
1291        HistoryListOutput = History.ListHistory(0, -1, "CIMS", "99X");
1292        historyLabel.setText(GUIHistoryFileName);
1293        historyArea.setText(HistoryListOutput);
1294        historyFrame.pack();
1295        historyFrame.setVisible(true);
1296        
1297    }
1298    
1299    private void modifyHistoryFile() {
1300
1301        if (GUIDeleteTF) {
1302            // delete records
1303            int DeleteReturn[] = History.DeleteHistoryRecords(runFullADTAnalysis,GUIHistoryFileName);
1304            logger.debug("deleted {} records... modified {} records", DeleteReturn[1],DeleteReturn[0]);
1305        } else if( GUICommentAddTF) {
1306            // 
1307            int CommentAddReturn = History.CommentHistoryRecords(GUICommentString);
1308            logger.debug("added comment to {} records",CommentAddReturn);
1309        } else {
1310            // invalid selection
1311            logger.warn("entered invalid selection!");
1312        }
1313        
1314        try {
1315            int HistoryFileRecords = History.WriteHistoryFile(GUIHistoryFileName);
1316            if (HistoryFileRecords >= 0) {
1317                logger.debug("wrote {} records to '{}'", HistoryFileRecords, GUIHistoryFileName);
1318            }
1319        } catch (IOException exception) {
1320            String ErrorMessage = String.format("error updating history file %s",GUIHistoryFileName);
1321            System.out.println(ErrorMessage);
1322            userMessage(ErrorMessage);
1323        }
1324    }
1325        
1326    private String selectHistoryFile() {
1327        
1328        String fileNameReturn = null;
1329        
1330        JFrame historyFileFrame = new JFrame();
1331        JFileChooser historyFileChooser = new JFileChooser();
1332        String historyPath = System.getenv("ODTHISTORY");
1333        if (historyPath == null) {
1334            historyPath = getLastPath("mcv.adt.lasthistorypath", System.getProperty("user.home"));
1335        }
1336        historyFileChooser.setCurrentDirectory(new File(historyPath));
1337        historyFileChooser.setDialogTitle("Select ADT History File");
1338        int returnVal = historyFileChooser.showOpenDialog(historyFileFrame);
1339        if (returnVal == JFileChooser.APPROVE_OPTION) {
1340            File file = historyFileChooser.getSelectedFile();
1341            fileNameReturn = file.getAbsolutePath();
1342            setLastPath("mcv.adt.lasthistorypath", file.getPath());
1343        }
1344        
1345        return fileNameReturn;
1346    }
1347    
1348    /**
1349     * Returns the path that corresponds to the given McIDAS-V property ID.
1350     *
1351     * @param id ID used to store user's last selected path.
1352     * @param defaultPath Path to use if {@code id} has not been set.
1353     *
1354     * @return Either the {@code String} representation of the last selected
1355     * path, or {@code defaultPath}.
1356     */
1357    private String getLastPath(String id, String defaultPath) {
1358        McIDASV mcv = (McIDASV)getIdv();
1359        String path = defaultPath;
1360        if (mcv != null) {
1361            path = mcv.getObjectStore().get(id, defaultPath);
1362        }
1363        return path;
1364    }
1365    
1366    /**
1367     * Sets the value of the given McIDAS-V property ID to the specified path.
1368     *
1369     * @param id ID to store.
1370     * @param path Path to associate with {@code id}.
1371     */
1372    private void setLastPath(String id, String path) {
1373        String okayPath = (path != null) ? path : "";
1374        McIDASV mcv = (McIDASV)getIdv();
1375        if (mcv != null) {
1376            XmlObjectStore store = mcv.getObjectStore();
1377            store.put(id, okayPath);
1378            store.saveIfNeeded();
1379        }
1380    }
1381    
1382    /**
1383     * Write a new ADT History File
1384     * @return true if ok
1385     */
1386    
1387    private String selectHistoryFileOutput() {
1388        
1389        File saveFile = null;
1390        String ErrorMessage;
1391        
1392        historyFileSaveChooser = new JFileChooser();
1393        historyFileSaveChooser.setCurrentDirectory(null);
1394        historyFileSaveChooser.setDialogTitle("Save ADT History File");
1395        int returnVal = historyFileSaveChooser.showSaveDialog(historyFrame);
1396        if (returnVal == JFileChooser.APPROVE_OPTION) {
1397            saveFile = historyFileSaveChooser.getSelectedFile();
1398            try (FileWriter outFile = new FileWriter(saveFile)) {
1399                outFile.write(HistoryListOutput);
1400                outFile.flush();
1401                outFile.close();
1402                ErrorMessage = String.format("success writing history file output file %s\n",saveFile.toString());
1403            } catch (IOException ex) {
1404                logger.error("problem writing to history file output", ex);
1405                ErrorMessage = String.format("error writing history file output file %s\n",saveFile.toString());
1406            }
1407            System.out.println(ErrorMessage);
1408            userMessage(ErrorMessage);
1409        }
1410            
1411        String saveFilePath = null;
1412        if (saveFile != null) {
1413            saveFilePath = saveFile.getAbsolutePath();
1414        }
1415        return saveFilePath;
1416
1417    }
1418    
1419    /**
1420     * Write out the ATCF file
1421     * @param outputstyle
1422     * @return true if written ok
1423     */
1424    
1425    private boolean ATCFFileOutput(int outputstyle) {
1426        File saveFile = null;
1427        String ATCFOutputFileName;
1428        String ATCFOutputFilePath;
1429        String ATCFFileOutput;
1430        String ATCFMessage;
1431        boolean writefileTF = false;
1432        boolean returnStatus = true;
1433        
1434        if (outputstyle == 0) {
1435            // output entire history file in ATCF
1436            historyFileSaveChooser = new JFileChooser();
1437            historyFileSaveChooser.setCurrentDirectory(null);
1438            historyFileSaveChooser.setDialogTitle("Write ATCF File");
1439            int returnVal = historyFileSaveChooser.showSaveDialog(historyFrame);
1440            if (returnVal == JFileChooser.APPROVE_OPTION) {
1441                saveFile = historyFileSaveChooser.getSelectedFile();
1442                writefileTF = true;
1443            } else if (returnVal == JFileChooser.CANCEL_OPTION) {
1444                // User has pressed cancel button
1445                writefileTF = false;
1446            }
1447            logger.debug("saving ATCF history listing file name={} writeTF={}", saveFile, writefileTF);
1448        } else {
1449            
1450            GUIATCFStormID = ATCFEntryStormTextField.getText();
1451            GUIATCFSiteID = ATCFEntrySiteTextField.getText();
1452            
1453            if ((GUIATCFStormID == null) || (GUIATCFSiteID == null)) {
1454                JOptionPane.showMessageDialog(this.getMainPanel(), "Please provide valid Storm and Site IDs for ATCF output.");
1455                return false;
1456            }
1457            
1458            // Validate the Storm ID and Site ID inputs
1459            boolean siteStormValid = true;
1460            // Storm must be 3-char
1461            if (GUIATCFStormID.length() != 3) {
1462                siteStormValid = false;
1463            } else {
1464                // It is 3-char, make sure it's DDC (digit-digit-char)
1465                if (! GUIATCFStormID.matches("\\d\\d[A-Z]")) {
1466                    siteStormValid = false;
1467                }
1468            }
1469            // Site must be 4-char
1470            if (GUIATCFSiteID.length() != 4) {
1471                siteStormValid = false;
1472            }
1473            
1474            if (! siteStormValid) {
1475                JOptionPane.showMessageDialog(null, "Please provide valid Storm and Site IDs for ATCF output.");
1476                return false;
1477            }
1478            
1479            // call routine to generate ATCF file name for single analysis record
1480            logger.debug("stormID={} siteID={}", GUIATCFStormID, GUIATCFSiteID);
1481            ATCFOutputFileName = Functions.adt_atcffilename(GUIATCFStormID,GUIATCFSiteID);
1482            logger.debug("atcf output name={}*", ATCFOutputFileName);
1483            ATCFOutputFilePath = System.getenv("ODTOUTPUT");
1484            if (ATCFOutputFilePath == null) {
1485                ATCFOutputFilePath = System.getenv("HOME");
1486            }
1487            logger.debug("atcf output path={}*", ATCFOutputFilePath);
1488            saveFile = new File(ATCFOutputFilePath + File.separator + ATCFOutputFileName);
1489            logger.debug("atcf output name={}*", saveFile.toString());
1490            writefileTF = true;
1491        }
1492        // call routine to output file
1493        logger.info("Site ID: " + GUIATCFSiteID + ", Storm ID: " + GUIATCFStormID);
1494        if ((GUIATCFSiteID == null) || (GUIATCFStormID == null)) {
1495            JOptionPane.showMessageDialog(historyFrame, "You must first activate ATCF output");
1496            return returnStatus;
1497        }
1498        ATCFFileOutput = History.ListHistory(outputstyle, GUIHistoryListFormat, GUIATCFSiteID, GUIATCFStormID);
1499        if (writefileTF) {
1500            try (FileWriter outFile = new FileWriter(saveFile)) {
1501                outFile.write(ATCFFileOutput);
1502                outFile.flush();
1503                outFile.close();
1504                ATCFMessage = String.format("Success writing ATCF file %s",saveFile);
1505            } catch (IOException ex) {
1506                logger.error("problem writing to ATCF file", ex);
1507                ATCFMessage = String.format("Error writing ATCF file %s",saveFile);
1508            }
1509            System.out.println(ATCFMessage);
1510            userMessage(ATCFMessage);
1511        }
1512        return returnStatus;
1513    }
1514    
1515    private String selectForecastFile() {
1516        
1517        String fileNameReturn = null;
1518        
1519        logger.debug("in selectForecastFile");
1520        JFrame forecastFileFrame = new JFrame();
1521        JFileChooser forecastFileChooser = new JFileChooser();
1522        String forecastPath = System.getenv("ODTAUTO");
1523        if (forecastPath == null) {
1524            forecastPath = getLastPath("mcv.adt.lastforecastpath", System.getProperty("user.home"));
1525        }
1526        logger.debug("forecast path={}", forecastPath);
1527        forecastFileChooser.setCurrentDirectory(new File(forecastPath));
1528        forecastFileChooser.setDialogTitle("Select ADT Forecast File");
1529        int returnVal = forecastFileChooser.showOpenDialog(forecastFileFrame);
1530        logger.debug("retVal={}", returnVal);
1531        if (returnVal == JFileChooser.APPROVE_OPTION) {
1532            File file = forecastFileChooser.getSelectedFile();
1533            fileNameReturn = file.getAbsolutePath();
1534            setLastPath("mcv.adt.lastforecastpath", file.getPath());
1535        } else {
1536            logger.error("error with file chooser");
1537        }
1538        return fileNameReturn;
1539    }
1540    
1541    private void getADTenvParameters() {
1542        History.InitCurrent(true);
1543        GUIHistoryFileName = null;
1544
1545        /* load initial ADT Environmental parameters */
1546        GUIDeleteTF = Env.DeleteTF;
1547        GUIRunAutoTF = Env.AutoTF;
1548        GUIOverrideSceneTF = Env.OverSceneTF;
1549        GUIOverrideTF = Env.OverTF;
1550        GUIATCFOutputTF = Env.ATCFOutputTF;
1551        GUIATCFRecordOutputTF = Env.ATCFRecordOutputTF;
1552        GUIInitStrengthTF = Env.InitStrengthTF;
1553        GUILandFlagTF = Env.LandFlagTF;
1554        GUIUseCKZTF = Env.UseCKZTF;
1555        GUIVmax1or10TF = Env.Vmax1or10TF;
1556        GUICommentAddTF = Env.CommentAddTF;
1557        GUIPMWActivateTF = Env.UsePMWTF;
1558        
1559        /* integer values */
1560        GUIDomainID = Env.DomainID;
1561        GUIForecastType = Env.ForecastFileType;
1562        GUIMWJulianDate = Env.MWJulianDate;
1563        GUIMWHHMMSSTime = Env.MWHHMMSSTime;
1564        GUIStartDate = Env.StartJulianDate;
1565        GUIStartTime = Env.StartHHMMSSTime;
1566        GUIEndDate = Env.EndJulianDate;
1567        GUIEndTime = Env.EndHHMMSSTime;
1568        GUIHistoryListFormat = Env.HistoryListFormat;
1569        /* double values */
1570        GUIRawTValue = Env.InitRawTValue;
1571        GUIMWScore = Env.MWScore;
1572        GUICKZGaleRadius = Env.CKZGaleRadius;
1573        GUICKZPenv = Env.CKZPenv;
1574        GUIRMWSize = Env.RMWSize;
1575        GUIUserLatitude = Env.SelectedLatitude;
1576        GUIUserLongitude = Env.SelectedLongitude;
1577        
1578        GUIForecastFileName = Env.ForecastFileName;            // needed?
1579        GUIHistoryFileListingName = Env.ASCIIOutputFileName;   // needed?
1580        GUIATCFStormID = Env.StormIDString;
1581        GUIATCFSiteID = Env.ATCFSourceAgcyIDString;
1582
1583    }
1584
1585    private void loadADTenvParameters() {
1586        /* Env GlobalVariables = new Env(); */
1587        
1588        logger.debug("setting env parameters");
1589        
1590        // send ADT Environmental parameters to Env prior to running ADT
1591        // boolean values
1592        Env.DeleteTF = GUIDeleteTF;
1593        Env.AutoTF = GUIRunAutoTF;
1594        Env.OverTF = GUIOverrideTF;
1595        Env.ATCFOutputTF = GUIATCFOutputTF;
1596        Env.ATCFRecordOutputTF = GUIATCFRecordOutputTF;
1597        Env.InitStrengthTF = GUIInitStrengthTF;
1598        Env.LandFlagTF = GUILandFlagTF;
1599        Env.UseCKZTF = GUIUseCKZTF;
1600        Env.Vmax1or10TF = GUIVmax1or10TF;
1601        Env.CommentAddTF = GUICommentAddTF;
1602        Env.OverSceneTF = GUIOverrideSceneTF;
1603        Env.UsePMWTF = GUIPMWActivateTF;
1604
1605        // integer values
1606        Env.DomainID = GUIDomainID;
1607        Env.ForecastFileType = GUIForecastType;
1608        Env.MWJulianDate = GUIMWJulianDate;
1609        Env.MWHHMMSSTime = GUIMWHHMMSSTime;
1610        Env.StartJulianDate = GUIStartDate;
1611        Env.StartHHMMSSTime = GUIStartTime;
1612        Env.EndJulianDate = GUIEndDate;
1613        Env.EndHHMMSSTime = GUIEndTime;
1614        Env.HistoryListFormat = GUIHistoryListFormat;
1615        // double values
1616        Env.InitRawTValue = GUIRawTValue;
1617        Env.MWScore = GUIMWScore;
1618        Env.CKZGaleRadius = GUICKZGaleRadius;
1619        Env.CKZPenv = GUICKZPenv;
1620        Env.RMWSize = GUIRMWSize;
1621        Env.SelectedLatitude = GUIUserLatitude;
1622        Env.SelectedLongitude = GUIUserLongitude;
1623        
1624        logger.debug("load forecast file name={}", GUIForecastFileName);
1625        Env.ForecastFileName = GUIForecastFileName;   // needed?
1626        Env.ASCIIOutputFileName = GUIHistoryFileListingName;   // needed?
1627        Env.StormIDString = GUIATCFStormID;
1628        Env.ATCFSourceAgcyIDString = GUIATCFSiteID;
1629        
1630    }
1631    
1632    private int ReadGUIOverrideInputFile(String GUIOverrideFile) {
1633        
1634        logger.debug("opening file '{}'", GUIOverrideFile);
1635        
1636        File GUIDataFile = new File(GUIOverrideFile);
1637        String delims = "[ ]+";
1638        String line;
1639        int retval = 1;
1640        
1641        GUIOverrideTF = false;
1642        GUIOverrideSceneTF = false;
1643        GUICommentString = null;
1644        GUIRunAutoTF = true;
1645        GUIDeleteTF = false;
1646        GUICommentAddTF = false;
1647        GUIStartDate = 1900001;
1648        GUIStartTime = 000000;
1649        GUIEndDate = 1900001;
1650        GUIEndTime = 000000;
1651        GUIUserLatitude = -99.5;
1652        GUIUserLongitude = -999.5;
1653        GUIDomainID = 0;
1654        runFullADTAnalysis = true;
1655        
1656        try {
1657            Scanner GUIFile = new Scanner(GUIDataFile);
1658            while (GUIFile.hasNextLine()) {
1659                if ((line = GUIFile.nextLine()).isEmpty()){
1660                    break;
1661                } else {
1662                    String[] tokens = line.split(delims);
1663                    String IDstring = tokens[0];
1664                    String RecValue = tokens[1];
1665                    /* System.out.println("scanning IDstring=%s\n",IDstring); */
1666                    switch (IDstring) {
1667                        case "ATCFOutputTF":
1668                            GUIATCFOutputTF = Boolean.valueOf(RecValue);
1669                            break;
1670                        case "ATCFRecordOutputTF":
1671                            GUIATCFRecordOutputTF = Boolean.valueOf(RecValue);
1672                            break;
1673                        case "InitStrengthTF":
1674                            GUIInitStrengthTF = Boolean.valueOf(RecValue);
1675                            break;
1676                        case "LandFlagTF":
1677                            GUILandFlagTF = Boolean.valueOf(RecValue);
1678                            break;
1679                        case "UseCKZTF":
1680                            GUIUseCKZTF = Boolean.valueOf(RecValue);
1681                            break;
1682                        case "Vmax1or10TF":
1683                            GUIVmax1or10TF = Boolean.valueOf(RecValue);
1684                            break;
1685                        case "UsePMWTF":
1686                            GUIPMWActivateTF = Boolean.valueOf(RecValue);
1687                            break;
1688                        case "ForecastType":
1689                            GUIForecastType = Integer.valueOf(RecValue);
1690                            break;
1691                        case "MWJulianDate":
1692                            GUIMWJulianDate = Integer.valueOf(RecValue);
1693                            break;
1694                        case "MWHHMMSSTime":
1695                            GUIMWHHMMSSTime = Integer.valueOf(RecValue);
1696                            break;
1697                        case "HistoryListFormat":
1698                            GUIHistoryListFormat = Integer.valueOf(RecValue);
1699                            break;
1700                        case "RawTValue":
1701                            GUIRawTValue = Double.valueOf(RecValue);
1702                            break;
1703                        case "MWScore":
1704                            GUIMWScore = Double.valueOf(RecValue);
1705                            break;
1706                        case "CKZGaleRadius":
1707                            GUICKZGaleRadius = Double.valueOf(RecValue);
1708                            break;
1709                        case "CKZPenv":
1710                            GUICKZPenv = Double.valueOf(RecValue);
1711                            break;
1712                        case "RMWSize":
1713                            GUIRMWSize = Double.valueOf(RecValue);
1714                            break;
1715                        case "HistoryFileName":
1716                            GUIHistoryFileName = RecValue;
1717                            break;
1718                        case "ForecastFileName":
1719                            GUIForecastFileName = RecValue;
1720                            break;
1721                        case "HistoryFileListingName":
1722                            GUIHistoryFileListingName = RecValue;
1723                            break;
1724                        case "ATCFStormID":
1725                            GUIATCFStormID = RecValue;
1726                            break;
1727                        case "ATCFSiteID":
1728                            GUIATCFSiteID = RecValue;
1729                            break;
1730                        default:
1731                            break;
1732                    }
1733                }
1734            }
1735            GUIFile.close();
1736        } catch (IOException ex) {
1737            retval = -1;
1738        }
1739        return retval;
1740    }
1741    
1742    public void latLonWidgetChanged() {
1743        logger.debug("latlonwidgetchanged called");
1744        try {
1745            logger.debug("latlon widget changed");
1746            String message = latLonWidget.isValidValues();
1747            if (message != null) {
1748                    userMessage(message);
1749                    return;
1750            }
1751            probeLocation = ucar.visad.Util.makeEarthLocation(
1752                            latLonWidget.getLat(), latLonWidget.getLon()).getLatLonPoint();
1753        } catch (Exception e) {
1754            logException("Handling LatLonWidget changed", e);
1755        }
1756    }
1757    
1758    protected boolean shouldAddDisplayListener() {
1759        return true;
1760    }
1761    
1762    protected boolean shouldAddControlListener() {
1763        return true;
1764    }
1765
1766    protected boolean canHandleEvents() {
1767        if (!getHaveInitialized() || (getMakeWindow() && !getWindowVisible())) {
1768            return false;
1769        }
1770        return isGuiShown();
1771    }
1772    
1773    public void handleDisplayChanged(DisplayEvent event) {
1774        super.handleDisplayChanged(event);
1775        if (canHandleEvents()) {
1776//            int id = event.getId();
1777//            // String idstring = event.toString();
1778//            // InputEvent inputEvent = event.getInputEvent();
1779//            // System.out.println("event ID=%d %s\n",id,idstring);
1780//            try {
1781//                if (id == DisplayEvent.MOUSE_PRESSED_LEFT) {
1782//                    logger.debug("Manual Position Selection");
1783//                    probeLocation = toEarth(event).getLatLonPoint();
1784//                    updateProbeLocation();
1785//                }
1786//            } catch (Exception e) {
1787//                logException("Error selecting position with mouse", e);
1788//            }
1789        }
1790    }
1791    
1792    /**
1793     * Respond to the probe being dragged.
1794     * 
1795     * @param event Event to handle.
1796     */
1797    @Override public void propertyChange(PropertyChangeEvent event) {
1798        if (canHandleEvents() && SelectorDisplayable.PROPERTY_POSITION.equals(event.getPropertyName())) {
1799            try {
1800                RealTuple position = probe.getPosition();
1801                double[] loc = position.getValues();
1802                logger.debug("Manual Position Selection loc={}", loc);
1803                // note: loc[1] is apparently latitude, and loc[0] is longitude!
1804                probeLocation = 
1805                    makeEarthLocation(loc[1], loc[0], loc[2]).getLatLonPoint();
1806                SwingUtilities.invokeLater(this::updatePositionWidget);
1807            } catch (VisADException | RemoteException ex) {
1808                logger.error("Error updating probe location", ex);
1809            }
1810        } else {
1811            super.propertyChange(event);
1812        }
1813    }
1814    
1815    /**
1816     * Update {@link #latLonWidget} if it exists.
1817     * 
1818     * <p>Note: must be called from the event dispatch thread.</p>
1819     */
1820    private void updatePositionWidget() {
1821        if (latLonWidget != null) {
1822            try {
1823                logger.trace("attempting to update widget! lat={} lon={}", probeLocation.getLatitude(), probeLocation.getLongitude());
1824                latLonWidget.setLat(getDisplayConventions().formatLatLon(probeLocation.getLatitude().getValue(CommonUnit.degree)));
1825                latLonWidget.setLon(getDisplayConventions().formatLatLon(probeLocation.getLongitude().getValue(CommonUnit.degree)));
1826            } catch (VisADException ex) {
1827                logger.error("Error updating GUI with probe position", ex);
1828            }
1829        } else {
1830            logger.trace("no lat/lon widget to update!");
1831        }
1832    }
1833    
1834    private void updateProbeLocation() {
1835        try {
1836            if (probeLocation == null) {
1837                return;
1838            }
1839            double lon = probeLocation.getLongitude().getValue(CommonUnit.degree);
1840            double lat = probeLocation.getLatitude().getValue(CommonUnit.degree);
1841            probe.setPosition(
1842                new RealTuple(RealTupleType.SpatialEarth3DTuple, new double[] { lon, lat, 0 }));
1843            probe.setVisible(true);
1844            
1845            GUIUserLatitude = lat;    // added TLO
1846            GUIUserLongitude = lon;    // added TLO
1847            logger.debug("set lat/lon from probe at lat={} lon={}", GUIUserLatitude, GUIUserLongitude);
1848            if (latLonWidget != null) {
1849                latLonWidget.setLat(getDisplayConventions().formatLatLon(
1850                                probeLocation.getLatitude().getValue(CommonUnit.degree)));
1851                latLonWidget.setLon(getDisplayConventions().formatLatLon(
1852                                probeLocation.getLongitude().getValue(CommonUnit.degree)));
1853            }
1854        } catch (Exception e) {
1855            logException("Handling probe changed", e);
1856        }
1857    }
1858
1859    /**
1860     * Set the ProbeLocation property.
1861     *
1862     * @param value New value for ProbeLocation.
1863     */
1864    public void setProbeLocation(LatLonPoint value) {
1865        probeLocation = value;
1866    }
1867
1868    /**
1869     * Get the ProbeLocation property.
1870     *
1871     * @return The ProbeLocation
1872     */
1873    public LatLonPoint getProbeLocation() {
1874            return probeLocation;
1875    }
1876        
1877    protected FlatField getFlatField(FieldImpl data)
1878        throws VisADException, RemoteException
1879    {
1880        FlatField ff;
1881        if (GridUtil.isSequence(data)) {
1882            ff = (FlatField)data.getSample(0);
1883        } else {
1884            ff = (FlatField)data;
1885        }
1886        return ff;
1887    }
1888
1889    public EarthLocation toEarth(DisplayEvent event)
1890        throws VisADException, RemoteException
1891    {
1892        NavigatedDisplay d = getNavigatedDisplay();
1893        return (d == null) ? null : d.getEarthLocation(toBox(event));
1894    }
1895
1896    private void GetImageDateTime() {
1897        
1898        RealTuple timeTuple;
1899        Real tt;
1900        DateTime dat;
1901        
1902        List infos = getDisplayInfos();
1903        DisplayInfo displayInfo = (DisplayInfo) infos.get(0);
1904        
1905        try {
1906            Animation anime = displayInfo.getViewManager().getAnimation();
1907            Set timeSet = anime.getSet();
1908            int pos = anime.getCurrent();
1909            
1910            timeTuple = DataUtility.getSample(timeSet, pos);
1911            tt = (Real) timeTuple.getComponent(0);
1912            dat = new DateTime(tt);
1913        } catch (VisADException e) {
1914            logException("Handling data", e);
1915            return;
1916        } catch (RemoteException f) {
1917            logger.warn("Something went wrong!", f);
1918            return;
1919        }
1920        
1921        double curdate = dat.getValue();
1922        logger.debug("curdate={}",curdate);
1923        
1924        Date datevalue = new Date((long)curdate*1000);
1925        
1926        SimpleDateFormat dateformat = new SimpleDateFormat("yyyyDDD");
1927        SimpleDateFormat timeformat = new SimpleDateFormat("HHmmss");
1928        dateformat.setTimeZone(TimeZone.getTimeZone("GMT"));
1929        timeformat.setTimeZone(TimeZone.getTimeZone("GMT"));
1930        
1931        String JulianDate = dateformat.format(datevalue);
1932        String HHMMSSTime = timeformat.format(datevalue);
1933        int ImageDateInt = Integer.valueOf(JulianDate);
1934        int ImageTimeInt = Integer.valueOf(HHMMSSTime);
1935        // System.out.println("image date = %d  image time=%d\n",ImageDateInt,ImageTimeInt); */
1936        
1937        Data.IRData_JulianDate = ImageDateInt;
1938        Data.IRData_HHMMSSTime = ImageTimeInt;
1939        
1940        logger.debug("IMAGE DATE={} TIME={}", Data.IRData_JulianDate, Data.IRData_HHMMSSTime);
1941    }
1942    
1943    private void GetImageData(float CenterLatitude, float CenterLongitude) {
1944        logger.debug("creating ReadIRImage()...");
1945        
1946        // ReadIRImage IRImage = new ReadIRImage();
1947        
1948        FlatField ffield;
1949        int SatelliteID;
1950        int channel;
1951        
1952        List sources = new ArrayList();
1953        
1954        logger.debug("entering getimagedata");
1955        boolean isTemp = false;
1956        choice.getDataSources(sources);
1957        try {
1958            List infos = getDisplayInfos();
1959            DataInstance de = getDataInstance();
1960            DisplayInfo displayInfo = (DisplayInfo) infos.get(0);
1961                
1962            Animation anime = displayInfo.getViewManager().getAnimation();
1963            // Set timeSet = anime.getSet();
1964            int pos = anime.getCurrent();
1965            ffield = DataUtil.getFlatField(de.getData());
1966            DataSourceImpl dsi = (DataSourceImpl) sources.get(0);
1967            
1968            if (dsi instanceof AddeImageDataSource) {
1969                ImageDataSource dds = (ImageDataSource) sources.get(0);
1970                List imageLists = dds.getImageList();
1971                
1972                AddeImageDescriptor aid = (AddeImageDescriptor) imageLists.get(pos);
1973                AreaDirectory ad = aid.getDirectory();
1974                SatelliteID = ad.getSensorID();
1975                int[] bands = ad.getBands();
1976                channel = bands[0];
1977                
1978                isTemp = Util.isCompatible(ffield, AirTemperature.getRealType());
1979            } else {
1980                channel = 4;
1981                SatelliteID = 70;
1982                // String name = ffield.getSample(0).getType().prettyString();
1983            }
1984        } catch (VisADException e) {
1985            logException("Handling data", e);
1986            return;
1987        } catch (RemoteException f) {
1988            logger.warn("Something went wrong!", f);
1989            return;
1990        }
1991            
1992        // String shortName = choice.getName();
1993        
1994        Env.UserDefineDomain = 0; // automated
1995        // String sidName = Functions.adt_sattypes(SatelliteID);
1996        
1997        logger.debug("SatelliteID={}", SatelliteID);
1998        
1999        try {
2000            ReadIRImage.ReadIRDataFile(ffield,
2001                                       CenterLatitude,
2002                                       CenterLongitude,
2003                                       SatelliteID,
2004                                       channel,
2005                                       isTemp);
2006        }
2007        catch (Exception ex) {
2008            logger.error("ReadIRImage failed", ex);
2009        }
2010    }
2011}