001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2023
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 http://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 -> {
727            WebBrowser.browse("https://www.ssec.wisc.edu/mcidas/software/v/resources/adt/McV_ADT_1p7.pdf");
728        });
729
730        GuiUtils.tmpInsets = GuiUtils.INSETS_5;
731        JComponent widgets =
732            GuiUtils.formLayout(
733                arr(left(hbox(arr(new JLabel("Storm Center Selection:"), manButton, autoButton), 5)),
734                    filler(),
735                    left(hbox(arr(manualStormSelectLabel), 10)),
736                    filler(),
737                    left(hbox(arr(filler(30, 1), latLonWidget))), filler(),
738                    left(hbox(arr(autoStormSelectLabel), 10)), filler(),
739                    left(hbox(arr(filler(30, 1), forecastBtn, forecastTypeBox,
740                        forecastSelectLabel, forecastLabel), 5)), filler(),
741                    left(hbox(arr(blankfield))),
742                    filler(1, 5),
743                    left(hbox(arr(new JLabel("HISTORY FILE INFORMATION")), 10)), filler(),
744                    left(hbox(arr(filler(30, 1), historyBtn, new JLabel
745                        ("Selected History File: "), selectedHistoryFile), 5)),
746                    filler(),
747                    left(hbox(arr(blankfield))),
748                    filler(1, 5),
749                    left(hbox(arr(new JLabel("PMW ANALYSIS")), 10)), filler(),
750                    left(hbox(arr(filler(30, 1), PMWActivateButton,
751                        pmwManDateLabel, pmwManDateTextField, pmwManTimeLabel,
752                        pmwManTimeTextField, pmwManScoreLabel, pmwManScoreTextField), 5)), filler(),
753                    left(hbox(arr(blankfield))),
754                    filler(1, 5),
755                    left(hbox(arr(new JLabel("MISCELLANEOUS OPTIONS")), 10)), filler(),
756                    left(hbox(arr(filler(30, 1), new JLabel("MSLP Conversion Method:"), mslpDvorakButton, mslpCKZButton, ckzPenvLabel, ckzPenvTextField, ckz34radiusLabel, ckz34radiusTextField), 5)), filler(),
757                    left(hbox(arr(filler(30, 1), sceneOverrideButton, OverrideLabel), 5)), filler(),
758                    left(hbox(arr(filler(30, 1), LandFlagLabel, LandONButton, LandOFFButton, filler(20, 1), VOutLabel, V1MinButton, V10MinButton, filler(20, 1), RawTLabel, RawTTextField, RMWLabel, RMWTextField), 5)), filler(),
759                    left(hbox(arr(filler(30, 1), ATCFOutputLabel, ATCFOutputButton, ATCFEntryStormLabel, ATCFEntryStormTextField, ATCFEntrySiteLabel, ATCFEntrySiteTextField), 5)), filler(),
760                    left(hbox(arr(filler(80, 1), adtBtn, listBtn, helpLinkLabel), 20)), filler()));
761                    
762        JPanel controls = topLeft(widgets);
763
764        /* set up ADT Bulletin display area */
765        resultArea = new JTextArea();
766        resultArea.setEditable(false);
767
768        Font c = new Font("Courier", Font.BOLD, 12);
769        
770        resultFrame = new JFrame("ADT Results");
771        resultFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
772        JScrollPane resultScroller = new JScrollPane(resultArea);
773        resultScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
774        resultFrame.add(resultScroller, BorderLayout.CENTER);
775        resultFrame.setPreferredSize(new Dimension(400, 600));
776        resultFrame.setFont(c);
777
778        /* set up ADT History File display area */
779        historyFrame = new JFrame("ADT History File Listing");
780        Container historyContainer = historyFrame.getContentPane();
781        historyFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
782        JPanel historyTextPanel = new JPanel();
783        FlowLayout historyListLayout = new FlowLayout();
784        historyTextPanel.setLayout(historyListLayout);
785        historyListLayout.setAlignment(FlowLayout.CENTER);
786           
787        historyArea = new JTextArea(50,150);
788        historyArea.setEditable(false);
789        JScrollPane historyScroller = new JScrollPane(historyArea);
790        historyScroller.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
791        historyScroller.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
792        historyScroller.setPreferredSize(new Dimension(1200, 400));
793        historyArea.setFont(c);
794
795        JPanel historyLabelPanel = new JPanel();
796        FlowLayout HistoryLabelLayout = new FlowLayout();
797        historyLabelPanel.setLayout(HistoryLabelLayout);
798        HistoryLabelLayout.setAlignment(FlowLayout.CENTER);
799        historyLabel = new JLabel("No History File Selected");
800        historyLabel.setPreferredSize(new Dimension(800, 20));
801        historyLabel.setFont(c);
802        
803        /* history file Editing Date Selection window */
804        JFrame historyDateFrame = new JFrame("History File Editor");
805        Container historyDateContainer = historyDateFrame.getContentPane();
806        historyDateFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
807        JPanel historyDatePanel = new JPanel();
808        FlowLayout DateStartEndLayout = new FlowLayout();
809        historyDatePanel.setLayout(DateStartEndLayout);
810        DateStartEndLayout.setAlignment(FlowLayout.CENTER);
811        JLabel historyDateStartLabel = new JLabel("Start:");
812        JLabel historyDateStartDateLabel = new JLabel("Date");
813        JTextField historyDateStartDateTextField = new JTextField("0000XXX00", 10);
814        JLabel historyDateStartTimeLabel = new JLabel("Time");
815        JTextField historyDateStartTimeTextField = new JTextField("-1", 8);
816        JLabel historyDateEndLabel = new JLabel("End");
817        JLabel historyDateEndDateLabel = new JLabel("Date");
818        JTextField historyDateEndDateTextField = new JTextField("0000XXX00", 10);
819        JLabel historyDateEndTimeLabel = new JLabel("Time");
820        JTextField historyDateEndTimeTextField = new JTextField("-1", 8);
821        
822        JPanel historyButtonPanel = new JPanel();
823        FlowLayout HistoryButtonLayout = new FlowLayout();
824        historyButtonPanel.setLayout(HistoryButtonLayout);
825        HistoryButtonLayout.setAlignment(FlowLayout.CENTER);
826
827        JButton historySaveListingBtn = new JButton("Write History");
828        historySaveListingBtn.setPreferredSize(new Dimension(200, 20));
829        historySaveListingBtn.addActionListener(ae -> {
830            GUIHistoryFileListingName = selectHistoryFileOutput();
831            logger.debug("saving history listing file name={}", GUIHistoryFileListingName);
832            GUIHistoryListFormat = -1;
833        });
834        JButton historyWriteATCFBtn = new JButton("Write ATCF");
835        historyWriteATCFBtn.setPreferredSize(new Dimension(200, 20));
836        historyWriteATCFBtn.addActionListener(ae -> {
837            GUIATCFOutputTF = true;
838            GUIHistoryListFormat = 0;
839            logger.debug("calling ATCFFileOutput");
840            ATCFFileOutput(0);
841        });
842        historyLabelPanel.add(historyLabel);
843        historyLabelPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
844        historyTextPanel.add(historyScroller);
845        historyTextPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
846
847        historyButtonPanel.add(historySaveListingBtn);
848        historyButtonPanel.add(historyWriteATCFBtn);
849        historyButtonPanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
850        historyContainer.add(historyLabelPanel,BorderLayout.NORTH);
851        historyContainer.add(historyTextPanel,BorderLayout.CENTER);
852        historyContainer.add(historyButtonPanel,BorderLayout.SOUTH);
853        
854        historyDateStartDateTextField.addActionListener(ae -> {
855            JTextField textField = (JTextField)ae.getSource();
856            GUIStartDate = Functions.cmonth2julian(textField.getText());
857        });
858        historyDateStartTimeTextField.addActionListener(ae -> {
859            JTextField textField = (JTextField)ae.getSource();
860            GUIStartTime = Integer.valueOf(textField.getText());
861        });
862        historyDateEndDateTextField.addActionListener(ae -> {
863            JTextField textField = (JTextField)ae.getSource();
864            GUIEndDate = Functions.cmonth2julian(textField.getText());
865        });
866        historyDateEndTimeTextField.addActionListener(ae -> {
867            JTextField textField = (JTextField)ae.getSource();
868            GUIEndTime = Integer.valueOf(textField.getText());
869        });
870        
871        JPanel historyDateButtonPanel = new JPanel();
872        FlowLayout DateButtonLayout = new FlowLayout();
873        historyDateButtonPanel.setLayout(DateButtonLayout);
874        DateButtonLayout.setAlignment(FlowLayout.CENTER);
875        JRadioButton historyEditDeleteButton = new JRadioButton("Delete Records");
876        historyEditDeleteButton.setActionCommand("Delete");
877        historyEditDeleteButton.setSelected(false);
878        JRadioButton historyEditAddCommentButton = new JRadioButton("Add Comment");
879        historyEditAddCommentButton.setActionCommand("Comment");
880        historyEditAddCommentButton.setSelected(false);
881        ButtonGroup editgroup = new ButtonGroup();
882        editgroup.add(historyEditDeleteButton);
883        editgroup.add(historyEditAddCommentButton);
884        JLabel historyEditAddCommentLabel = new JLabel("Comment:");
885        JTextField historyEditAddCommentTextField = new JTextField("no comment entered", 25);
886        historyEditAddCommentTextField.setEnabled(false);
887        
888        historyEditDeleteButton.addActionListener(ae -> {
889            // history Edit - Delete
890            historyEditDeleteButton.setSelected(true);
891            historyEditAddCommentButton.setSelected(false);
892            historyEditAddCommentLabel.setEnabled(false);
893            historyEditAddCommentTextField.setEnabled(false);
894            GUICommentAddTF = false;
895            GUIDeleteTF = true;
896        });
897        
898        historyEditAddCommentButton.addActionListener(ae -> {
899            // history Edit - Add Comment
900            historyEditDeleteButton.setSelected(false);
901            historyEditAddCommentButton.setSelected(true);
902            historyEditAddCommentLabel.setEnabled(true);
903            historyEditAddCommentTextField.setEnabled(true);
904            GUICommentAddTF = true;
905            GUIDeleteTF = false;
906        });
907        historyEditAddCommentTextField.addActionListener(ae -> {
908            JTextField src = (JTextField)ae.getSource();
909            GUICommentString = src.getText();
910        });
911        JPanel historyEditInputPanel = new JPanel();
912        FlowLayout EditInputButtonLayout = new FlowLayout();
913        historyEditInputPanel.setLayout(EditInputButtonLayout);
914        EditInputButtonLayout.setAlignment(FlowLayout.CENTER);
915        JButton historyEditApplyButton = new JButton("Apply Edits");
916        historyEditApplyButton.setPreferredSize(new Dimension(150, 20));
917        historyEditApplyButton.addActionListener(ae -> modifyHistoryFile());
918        JButton historyEditCancelButton = new JButton("Cancel");
919        historyEditCancelButton.setPreferredSize(new Dimension(150, 20));
920        historyEditCancelButton.addActionListener(ae -> historyDateFrame.dispose());
921        historyDatePanel.add(historyDateStartLabel);
922        historyDatePanel.add(historyDateStartDateLabel);
923        historyDatePanel.add(historyDateStartDateTextField);
924        historyDatePanel.add(historyDateStartTimeLabel);
925        historyDatePanel.add(historyDateStartTimeTextField);
926        historyDatePanel.add(historyDateEndLabel);
927        historyDatePanel.add(historyDateEndDateLabel);
928        historyDatePanel.add(historyDateEndDateTextField);
929        historyDatePanel.add(historyDateEndTimeLabel);
930        historyDatePanel.add(historyDateEndTimeTextField);
931        historyDatePanel.setComponentOrientation(ComponentOrientation.LEFT_TO_RIGHT);
932        historyDateButtonPanel.add(historyEditDeleteButton);
933        historyDateButtonPanel.add(historyEditAddCommentButton);
934        historyDateButtonPanel.add(historyEditAddCommentLabel);
935        historyDateButtonPanel.add(historyEditAddCommentTextField);
936        historyEditInputPanel.add(historyEditApplyButton);
937        historyEditInputPanel.add(historyEditCancelButton);
938        historyDateContainer.add(historyDatePanel, BorderLayout.NORTH);
939        historyDateContainer.add(historyDateButtonPanel, BorderLayout.CENTER);
940        historyDateContainer.add(historyEditInputPanel, BorderLayout.SOUTH);
941
942        /* set up Scene Type Override Window display window */
943        overrideSceneFrame = new JFrame("Override Scene Type");
944        overrideSceneFrame.setSize(new Dimension(400, 300));
945        Container overrideSceneContainer = overrideSceneFrame.getContentPane();
946        overrideSceneFrame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
947        JPanel overrideSceneCurrentPanel = new JPanel();
948        FlowLayout OverrideSceneCurrentLayout = new FlowLayout();
949        overrideSceneCurrentPanel.setLayout(OverrideSceneCurrentLayout);
950        OverrideSceneCurrentLayout.setAlignment(FlowLayout.CENTER);
951        JLabel overrideSceneCurrentLabel = new JLabel("Current Scene Type:");
952        overrideSceneCurrentValueLabel = new JLabel(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
953        JPanel overrideSceneSelectPanel = new JPanel();
954        FlowLayout OverrideSceneSelectLayout = new FlowLayout();
955        overrideSceneCurrentPanel.setLayout(OverrideSceneSelectLayout);
956        OverrideSceneSelectLayout.setAlignment(FlowLayout.CENTER);
957        JLabel overrideSceneSelectLabel = new JLabel("Select New Scene Type:");
958        overrideSceneTypeBox = new JComboBox<>(SCENE_TYPES);
959        overrideSceneTypeBox.setSelectedIndex(Env.OverrideSceneTypeIndex);
960        overrideSceneTypeBox.setPreferredSize(new Dimension(150, 20));
961        // overrideSceneTypeBox.addActionListener(ame -> Env.OverrideSceneTypeIndex = overrideSceneTypeBox.getSelectedIndex());
962        JPanel overrideSceneButtonPanel = new JPanel();
963        FlowLayout OverrideSceneButtonLayout = new FlowLayout();
964        overrideSceneButtonPanel.setLayout(OverrideSceneButtonLayout);
965        OverrideSceneButtonLayout.setAlignment(FlowLayout.CENTER);
966        JButton overrideSceneAcceptButton = new JButton("Accept New Scene");
967        overrideSceneAcceptButton.setPreferredSize(new Dimension(190, 20));
968        overrideSceneAcceptButton.addActionListener(ae -> {
969            // accept new scene selection
970            overrideSceneFrame.setVisible(false);
971            Env.OverrideSceneTypeIndex = overrideSceneTypeBox.getSelectedIndex();
972            OverrideLabel.setText(SCENE_TYPE_PREFIX + SCENE_TYPES[Env.OverrideSceneTypeIndex]);
973            overrideSceneCurrentValueLabel.setText(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
974            // runADTmain();
975        });
976        JButton overrideSceneCancelButton = new JButton("Keep Current Scene");
977        overrideSceneCancelButton.setPreferredSize(new Dimension(190, 20));
978        overrideSceneCancelButton.addActionListener(ae -> {
979            overrideSceneFrame.setVisible(false);
980            // runADTmain();
981        });
982        overrideSceneCurrentPanel.add(overrideSceneCurrentLabel);
983        overrideSceneCurrentPanel.add(overrideSceneCurrentValueLabel);
984        overrideSceneSelectPanel.add(overrideSceneSelectLabel);
985        overrideSceneSelectPanel.add(overrideSceneTypeBox);
986        overrideSceneButtonPanel.add(overrideSceneAcceptButton);
987        overrideSceneButtonPanel.add(overrideSceneCancelButton);
988        overrideSceneContainer.add(overrideSceneCurrentPanel, BorderLayout.NORTH);
989        overrideSceneContainer.add(overrideSceneSelectPanel, BorderLayout.CENTER);
990        overrideSceneContainer.add(overrideSceneButtonPanel, BorderLayout.SOUTH);
991
992        JScrollPane scrollPane = new JScrollPane(controls);
993        scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
994        return scrollPane;
995    }
996
997    /**
998     * Do some cursory checking on validity of selected History file
999     * @param historyFileName
1000     * @return true is seems ok
1001     */
1002    
1003    private boolean validHistoryFile(String historyFileName) {
1004        boolean seemsOk = true;
1005        
1006        History CurrentHistory = new History();
1007        
1008        try {
1009            logger.debug("trying to read history file {}", historyFileName);
1010            CurrentHistory.ReadHistoryFile(historyFileName);
1011        } catch (IOException exception) {
1012            logger.warn("History file %s is not valid", historyFileName);
1013            seemsOk = false;
1014        }
1015        
1016        logger.debug("Number of history records: {}", History.HistoryNumberOfRecords());
1017        if (History.HistoryNumberOfRecords() == 0) seemsOk = false;
1018        return seemsOk;
1019    }
1020
1021    @Override
1022    protected void getHelpMenuItems(List items, boolean forMenuBar) {
1023        items.add(GuiUtils.makeMenuItem("Details", this, "showDetails"));
1024        JMenuItem jmi = new JMenuItem("User's Guide");
1025        GuiUtils.setIcon(jmi, "/auxdata/ui/icons/help.png");
1026        jmi.addActionListener(e -> {
1027            WebBrowser.browse("https://www.ssec.wisc.edu/mcidas/software/v/resources/adt/McV_ADT_1p7.pdf");
1028        });
1029        items.add(jmi);
1030    }
1031
1032    private void runADTmain() {
1033        if (!running) {
1034            running = true;
1035            adtBtn.setEnabled(false);
1036            adtBtn.setText("Running");
1037            Misc.run(() -> {
1038                runADT();
1039                ExitADT();
1040            });
1041        }
1042    }
1043        
1044    private void runADT() {
1045        Main StormADT = new Main();
1046        String ADTRunOutput;
1047        String ErrorMessage;
1048        
1049        if (GUIFileOverrideTF) {
1050            String GUIOverrideFilePath = System.getenv("ODTHOME");
1051            if (GUIOverrideFilePath == null) {
1052                GUIOverrideFilePath = System.getenv("HOME");
1053            }
1054            String GUIOverrideFile = GUIOverrideFilePath + "/runadt.nogui.inputs.txt";
1055            /* GUIFileOverrideCheckBoxToggle();  change toggle back to OFF */
1056            int RetVal = ReadGUIOverrideInputFile(GUIOverrideFile);
1057            if (RetVal == -1) {
1058                ErrorMessage = String.format("Error reading GUI override file %s\n",GUIOverrideFile);
1059                System.out.println(ErrorMessage);
1060                userMessage(ErrorMessage);
1061                ExitADT();
1062                return;
1063            }
1064        }
1065        
1066        loadADTenvParameters();
1067        
1068        boolean RunAuto = Env.AutoTF;
1069        
1070        // In auto mode, make sure a valid forecast file was selected
1071        if (RunAuto) {
1072            if (GUIForecastFileName == null) {
1073                userMessage("A valid forecast file must be selected to use Automated mode.");
1074                ExitADT();
1075                return;
1076            }
1077        }
1078
1079        /* set storm position either through automated storm selection or by manual choice */
1080        GetImageDateTime();
1081        int ReturnVal = StormADT.GetInitialPosition();  // should set up to throw exception instead of return value
1082        if (ReturnVal < 0) {
1083            ErrorMessage = "Error obtaining initial position... exiting ADT\n";
1084            System.out.println(ErrorMessage);
1085            userMessage(ErrorMessage);
1086            ExitADT();
1087        } else {
1088            if (RunAuto) {
1089                try {
1090                    float CenterLatitude = (float)Env.SelectedLatitude;
1091                    float CenterLongitude =  (float)Env.SelectedLongitude;
1092                    /* System.out.println("pre-ARCHER latitude=%f longitude=%f\n",CenterLatitude,CenterLongitude); */
1093                    GetImageData(CenterLatitude, CenterLongitude);
1094                } catch (Exception exception) {
1095                    ErrorMessage = "Error reading IR data pre-ARCHER\n";
1096                    System.out.println(ErrorMessage);
1097                    userMessage(ErrorMessage);
1098                    ExitADT();
1099                    return;
1100                }
1101                StormADT.GetARCHERPosition();
1102            } else {
1103                if (probeLocation == null) {
1104                    ErrorMessage = "Please select storm center location manually and try again";
1105                    System.out.println(ErrorMessage);
1106                    userMessage(ErrorMessage);
1107                    ExitADT();
1108                    return;
1109                } else {
1110                    Env.SelectedLatitude = probeLocation.getLatitude().getValue();
1111                    Env.SelectedLongitude = probeLocation.getLongitude().getValue();
1112                }
1113            }
1114            
1115            try {
1116                float CenterLatitude = (float) Env.SelectedLatitude;
1117                float CenterLongitude =  (float) Env.SelectedLongitude;
1118                /* System.out.println("latitude=%f longitude=%f domain=%d\n",CenterLatitude,CenterLongitude,DomainID); */
1119                GetImageData(CenterLatitude, CenterLongitude);
1120            } catch (Exception e) {
1121                ErrorMessage = "Error reading IR data in getimagedata()\n";
1122                logger.error(ErrorMessage.trim(), e);
1123                userMessage(ErrorMessage);
1124                ExitADT();
1125                return;
1126            }
1127            
1128            // TJJ Jun 2017 Just about ready, a few more validation checks and we can run          
1129            // If CKZ chosen as MSLP Conversion Method, need to validate Penv and 34kt Radius fields
1130            // This may not be the best place to do this, but it's better than not doing it ;-)
1131            
1132            if (GUIUseCKZTF) {
1133                
1134                String newPenvStr = ckzPenvTextField.getText();
1135                boolean badPenv = false;
1136                try {
1137                    int newPenv = Integer.valueOf(newPenvStr);
1138                    if (newPenv > 0) {
1139                        GUICKZPenv = newPenv;
1140                        Env.CKZPenv = GUICKZPenv;
1141                    } else {
1142                        badPenv = true;
1143                    }
1144                } catch (NumberFormatException nfe) {
1145                    badPenv = true;
1146                }
1147                
1148                if (badPenv) {
1149                    // Throw up a warning and bail out
1150                    showBadIntWarning("Penv", newPenvStr);
1151                    return;
1152                }
1153                
1154                String newRadiusStr = ckz34radiusTextField.getText();
1155                boolean badNewRadius = false;
1156                try {
1157                    int newRadius = Integer.valueOf(newRadiusStr);
1158                    if (newRadius > 0) {
1159                        GUICKZGaleRadius = newRadius;
1160                        Env.CKZGaleRadius = GUICKZGaleRadius;
1161                    } else {
1162                        badNewRadius = true;
1163                    }
1164                } catch (NumberFormatException nfe) {
1165                    badNewRadius = true;
1166                }
1167                
1168                if (badNewRadius) {
1169                    // Throw up a warning and bail out
1170                    showBadIntWarning("Radius", newRadiusStr);
1171                    return;
1172                }
1173                
1174            }
1175             
1176            try {
1177                logger.debug("RUNNING ADT ANALYSIS");
1178                ADTRunOutput = StormADT.RunADTAnalysis(runFullADTAnalysis,GUIHistoryFileName);
1179            } catch (IOException exception) {
1180                ErrorMessage = "Error with call to StormADT.RunADT()\n";
1181                logger.error(ErrorMessage.trim(), exception);
1182                userMessage(ErrorMessage);
1183                ExitADT();
1184                return;
1185            }
1186            if (GUIOverrideSceneTF) {
1187                /* System.out.println("Overriding scene type!!!  Scene value=%d\n",InitialSceneTypeValue); */
1188                overrideSceneCurrentValueLabel.setText(SCENE_TYPES[Env.OverrideSceneTypeIndex]);
1189                overrideSceneFrame.pack();
1190                overrideSceneFrame.setVisible(true);
1191                ExitADT();
1192            } else {
1193                logger.debug("done running ADT");
1194    
1195                resultArea.setText(ADTRunOutput);
1196                resultFrame.pack();
1197                resultFrame.setVisible(true);
1198 
1199                // TJJ Dec 2017
1200                // This is in reference to Request #11, Bug #17 from
1201                // http://mcidas.ssec.wisc.edu/inquiry-v/?inquiry=1187
1202                // Since the intent here is to modify the currently active history file by appending
1203                // one record, and since that record insert had been previously commented out below,
1204                // we'll assume this was never working properly in the first place.  To prevent the
1205                // current History File from being clobbered, we just won't do the re-write for now,
1206                // since as is, a deep Exception zeros out the file, and the original file should 
1207                // at the very least remain unmodified.
1208                
1209//                if (GUIHistoryFileName != null) {
1210//                    try {
1211//                        // int[] InsertRecs = History.InsertHistoryRecord(runFullADTAnalysis,GUIHistoryFileName);
1212//                        /* System.out.println("*** Modified=%d InsertOverwriteFlag=%d***\n",InsertRecs[0],InsertRecs[1]); */
1213//                        int NumRecs = History.WriteHistoryFile(GUIHistoryFileName);
1214//                        ErrorMessage = String.format("Number of records written to history file: %d\n", NumRecs);
1215//                    } catch (IOException exception) {
1216//                        ErrorMessage = String.format("Error writing history file %s\n", GUIHistoryFileName);
1217//                    } catch (Exception e) {
1218//                        logger.error("Exception: ", e);
1219//                        ErrorMessage = String.format("Error writing history file %s\n", GUIHistoryFileName);
1220//                    }
1221//                    logger.warn(ErrorMessage.trim());
1222//                    userMessage(ErrorMessage);
1223//                }
1224                
1225                if (GUIATCFRecordOutputTF) {
1226                    ATCFFileOutput(-1);
1227                }
1228                
1229                ExitADT();
1230            }
1231        }
1232    }
1233    
1234    /**
1235     * Show a warning about a certain parameter needing to be greater than zero.
1236     * 
1237     * @param type Parameter name. Cannot be {@code null}.
1238     * @param badValue Erroneous value. Cannot be {@code null}.
1239     */
1240    private void showBadIntWarning(String type, String badValue) {
1241        String msg = "Invalid %s value: %s\nPlease provide a positive integer.";
1242        JOptionPane.showMessageDialog(null, 
1243                                      String.format(msg, type, badValue));
1244        ExitADT();
1245    }
1246    
1247    private void ExitADT() {
1248        running = false;
1249        adtBtn.setEnabled(true);
1250        adtBtn.setText("Run Analysis");
1251    }
1252    
1253    /*
1254     * Override for additional local cleanup
1255     * (non-Javadoc)
1256     * @see ucar.unidata.idv.control.DisplayControlImpl#doRemove()
1257     */
1258
1259    @Override public void doRemove() throws RemoteException, VisADException {
1260        super.doRemove();
1261        if (resultFrame != null) {
1262            resultFrame.dispose();
1263        }
1264        if (historyFrame != null) {
1265            historyFrame.dispose();
1266        }
1267    }
1268
1269    private void listHistoryFile() {
1270        HistoryListOutput = null;
1271        
1272        History CurrentHistory = new History();
1273        
1274        // Make sure a valid History File has been selected. At startup, value will be null
1275        if (GUIHistoryFileName == null) {
1276            JOptionPane.showMessageDialog(null, 
1277            "Please first select a valid ADT History File.");
1278            return;
1279        }
1280        
1281        try {
1282            logger.debug("trying to read history file {}", GUIHistoryFileName);
1283            CurrentHistory.ReadHistoryFile(GUIHistoryFileName);
1284        } catch (IOException exception) {
1285            String ErrorMessage = String.format("History file %s is not found",GUIHistoryFileName);
1286            logger.warn(ErrorMessage);
1287            userMessage(ErrorMessage);
1288            return;
1289        }
1290        
1291        logger.debug("Number of history records: {}", History.HistoryNumberOfRecords());
1292        
1293        HistoryListOutput = History.ListHistory(0, -1, "CIMS", "99X");
1294        historyLabel.setText(GUIHistoryFileName);
1295        historyArea.setText(HistoryListOutput);
1296        historyFrame.pack();
1297        historyFrame.setVisible(true);
1298        
1299    }
1300    
1301    private void modifyHistoryFile() {
1302
1303        if (GUIDeleteTF) {
1304            // delete records
1305            int DeleteReturn[] = History.DeleteHistoryRecords(runFullADTAnalysis,GUIHistoryFileName);
1306            logger.debug("deleted {} records... modified {} records", DeleteReturn[1],DeleteReturn[0]);
1307        } else if( GUICommentAddTF) {
1308            // 
1309            int CommentAddReturn = History.CommentHistoryRecords(GUICommentString);
1310            logger.debug("added comment to {} records",CommentAddReturn);
1311        } else {
1312            // invalid selection
1313            logger.warn("entered invalid selection!");
1314        }
1315        
1316        try {
1317            int HistoryFileRecords = History.WriteHistoryFile(GUIHistoryFileName);
1318            if (HistoryFileRecords >= 0) {
1319                logger.debug("wrote {} records to '{}'", HistoryFileRecords, GUIHistoryFileName);
1320            }
1321        } catch (IOException exception) {
1322            String ErrorMessage = String.format("error updating history file %s",GUIHistoryFileName);
1323            System.out.println(ErrorMessage);
1324            userMessage(ErrorMessage);
1325        }
1326    }
1327        
1328    private String selectHistoryFile() {
1329        
1330        String fileNameReturn = null;
1331        
1332        JFrame historyFileFrame = new JFrame();
1333        JFileChooser historyFileChooser = new JFileChooser();
1334        String historyPath = System.getenv("ODTHISTORY");
1335        if (historyPath == null) {
1336            historyPath = getLastPath("mcv.adt.lasthistorypath", System.getProperty("user.home"));
1337        }
1338        historyFileChooser.setCurrentDirectory(new File(historyPath));
1339        historyFileChooser.setDialogTitle("Select ADT History File");
1340        int returnVal = historyFileChooser.showOpenDialog(historyFileFrame);
1341        if (returnVal == JFileChooser.APPROVE_OPTION) {
1342            File file = historyFileChooser.getSelectedFile();
1343            fileNameReturn = file.getAbsolutePath();
1344            setLastPath("mcv.adt.lasthistorypath", file.getPath());
1345        }
1346        
1347        return fileNameReturn;
1348    }
1349    
1350    /**
1351     * Returns the path that corresponds to the given McIDAS-V property ID.
1352     *
1353     * @param id ID used to store user's last selected path.
1354     * @param defaultPath Path to use if {@code id} has not been set.
1355     *
1356     * @return Either the {@code String} representation of the last selected
1357     * path, or {@code defaultPath}.
1358     */
1359    private String getLastPath(String id, String defaultPath) {
1360        McIDASV mcv = (McIDASV)getIdv();
1361        String path = defaultPath;
1362        if (mcv != null) {
1363            path = mcv.getObjectStore().get(id, defaultPath);
1364        }
1365        return path;
1366    }
1367    
1368    /**
1369     * Sets the value of the given McIDAS-V property ID to the specified path.
1370     *
1371     * @param id ID to store.
1372     * @param path Path to associate with {@code id}.
1373     */
1374    private void setLastPath(String id, String path) {
1375        String okayPath = (path != null) ? path : "";
1376        McIDASV mcv = (McIDASV)getIdv();
1377        if (mcv != null) {
1378            XmlObjectStore store = mcv.getObjectStore();
1379            store.put(id, okayPath);
1380            store.saveIfNeeded();
1381        }
1382    }
1383    
1384    /**
1385     * Write a new ADT History File
1386     * @return true if ok
1387     */
1388    
1389    private String selectHistoryFileOutput() {
1390        
1391        File saveFile = null;
1392        String ErrorMessage;
1393        
1394        historyFileSaveChooser = new JFileChooser();
1395        historyFileSaveChooser.setCurrentDirectory(null);
1396        historyFileSaveChooser.setDialogTitle("Save ADT History File");
1397        int returnVal = historyFileSaveChooser.showSaveDialog(historyFrame);
1398        if (returnVal == JFileChooser.APPROVE_OPTION) {
1399            saveFile = historyFileSaveChooser.getSelectedFile();
1400            try (FileWriter outFile = new FileWriter(saveFile)) {
1401                outFile.write(HistoryListOutput);
1402                outFile.flush();
1403                outFile.close();
1404                ErrorMessage = String.format("success writing history file output file %s\n",saveFile.toString());
1405            } catch (IOException ex) {
1406                logger.error("problem writing to history file output", ex);
1407                ErrorMessage = String.format("error writing history file output file %s\n",saveFile.toString());
1408            }
1409            System.out.println(ErrorMessage);
1410            userMessage(ErrorMessage);
1411        }
1412            
1413        String saveFilePath = null;
1414        if (saveFile != null) {
1415            saveFilePath = saveFile.getAbsolutePath();
1416        }
1417        return saveFilePath;
1418
1419    }
1420    
1421    /**
1422     * Write out the ATCF file
1423     * @param outputstyle
1424     * @return true if written ok
1425     */
1426    
1427    private boolean ATCFFileOutput(int outputstyle) {
1428        File saveFile = null;
1429        String ATCFOutputFileName;
1430        String ATCFOutputFilePath;
1431        String ATCFFileOutput;
1432        String ATCFMessage;
1433        boolean writefileTF = false;
1434        boolean returnStatus = true;
1435        
1436        if (outputstyle == 0) {
1437            // output entire history file in ATCF
1438            historyFileSaveChooser = new JFileChooser();
1439            historyFileSaveChooser.setCurrentDirectory(null);
1440            historyFileSaveChooser.setDialogTitle("Write ATCF File");
1441            int returnVal = historyFileSaveChooser.showSaveDialog(historyFrame);
1442            if (returnVal == JFileChooser.APPROVE_OPTION) {
1443                saveFile = historyFileSaveChooser.getSelectedFile();
1444                writefileTF = true;
1445            } else if (returnVal == JFileChooser.CANCEL_OPTION) {
1446                // User has pressed cancel button
1447                writefileTF = false;
1448            }
1449            logger.debug("saving ATCF history listing file name={} writeTF={}", saveFile, writefileTF);
1450        } else {
1451            
1452            GUIATCFStormID = ATCFEntryStormTextField.getText();
1453            GUIATCFSiteID = ATCFEntrySiteTextField.getText();
1454            
1455            if ((GUIATCFStormID == null) || (GUIATCFSiteID == null)) {
1456                JOptionPane.showMessageDialog(this.getMainPanel(), "Please provide valid Storm and Site IDs for ATCF output.");
1457                return false;
1458            }
1459            
1460            // Validate the Storm ID and Site ID inputs
1461            boolean siteStormValid = true;
1462            // Storm must be 3-char
1463            if (GUIATCFStormID.length() != 3) {
1464                siteStormValid = false;
1465            } else {
1466                // It is 3-char, make sure it's DDC (digit-digit-char)
1467                if (! GUIATCFStormID.matches("\\d\\d[A-Z]")) {
1468                    siteStormValid = false;
1469                }
1470            }
1471            // Site must be 4-char
1472            if (GUIATCFSiteID.length() != 4) {
1473                siteStormValid = false;
1474            }
1475            
1476            if (! siteStormValid) {
1477                JOptionPane.showMessageDialog(null, "Please provide valid Storm and Site IDs for ATCF output.");
1478                return false;
1479            }
1480            
1481            // call routine to generate ATCF file name for single analysis record
1482            logger.debug("stormID={} siteID={}", GUIATCFStormID, GUIATCFSiteID);
1483            ATCFOutputFileName = Functions.adt_atcffilename(GUIATCFStormID,GUIATCFSiteID);
1484            logger.debug("atcf output name={}*", ATCFOutputFileName);
1485            ATCFOutputFilePath = System.getenv("ODTOUTPUT");
1486            if (ATCFOutputFilePath == null) {
1487                ATCFOutputFilePath = System.getenv("HOME");
1488            }
1489            logger.debug("atcf output path={}*", ATCFOutputFilePath);
1490            saveFile = new File(ATCFOutputFilePath + File.separator + ATCFOutputFileName);
1491            logger.debug("atcf output name={}*", saveFile.toString());
1492            writefileTF = true;
1493        }
1494        // call routine to output file
1495        logger.info("Site ID: " + GUIATCFSiteID + ", Storm ID: " + GUIATCFStormID);
1496        if ((GUIATCFSiteID == null) || (GUIATCFStormID == null)) {
1497            JOptionPane.showMessageDialog(historyFrame, "You must first activate ATCF output");
1498            return returnStatus;
1499        }
1500        ATCFFileOutput = History.ListHistory(outputstyle, GUIHistoryListFormat, GUIATCFSiteID, GUIATCFStormID);
1501        if (writefileTF) {
1502            try (FileWriter outFile = new FileWriter(saveFile)) {
1503                outFile.write(ATCFFileOutput);
1504                outFile.flush();
1505                outFile.close();
1506                ATCFMessage = String.format("Success writing ATCF file %s",saveFile);
1507            } catch (IOException ex) {
1508                logger.error("problem writing to ATCF file", ex);
1509                ATCFMessage = String.format("Error writing ATCF file %s",saveFile);
1510            }
1511            System.out.println(ATCFMessage);
1512            userMessage(ATCFMessage);
1513        }
1514        return returnStatus;
1515    }
1516    
1517    private String selectForecastFile() {
1518        
1519        String fileNameReturn = null;
1520        
1521        logger.debug("in selectForecastFile");
1522        JFrame forecastFileFrame = new JFrame();
1523        JFileChooser forecastFileChooser = new JFileChooser();
1524        String forecastPath = System.getenv("ODTAUTO");
1525        if (forecastPath == null) {
1526            forecastPath = getLastPath("mcv.adt.lastforecastpath", System.getProperty("user.home"));
1527        }
1528        logger.debug("forecast path={}", forecastPath);
1529        forecastFileChooser.setCurrentDirectory(new File(forecastPath));
1530        forecastFileChooser.setDialogTitle("Select ADT Forecast File");
1531        int returnVal = forecastFileChooser.showOpenDialog(forecastFileFrame);
1532        logger.debug("retVal={}", returnVal);
1533        if (returnVal == JFileChooser.APPROVE_OPTION) {
1534            File file = forecastFileChooser.getSelectedFile();
1535            fileNameReturn = file.getAbsolutePath();
1536            setLastPath("mcv.adt.lastforecastpath", file.getPath());
1537        } else {
1538            logger.error("error with file chooser");
1539        }
1540        return fileNameReturn;
1541    }
1542    
1543    private void getADTenvParameters() {
1544        History.InitCurrent(true);
1545        GUIHistoryFileName = null;
1546
1547        /* load initial ADT Environmental parameters */
1548        GUIDeleteTF = Env.DeleteTF;
1549        GUIRunAutoTF = Env.AutoTF;
1550        GUIOverrideSceneTF = Env.OverSceneTF;
1551        GUIOverrideTF = Env.OverTF;
1552        GUIATCFOutputTF = Env.ATCFOutputTF;
1553        GUIATCFRecordOutputTF = Env.ATCFRecordOutputTF;
1554        GUIInitStrengthTF = Env.InitStrengthTF;
1555        GUILandFlagTF = Env.LandFlagTF;
1556        GUIUseCKZTF = Env.UseCKZTF;
1557        GUIVmax1or10TF = Env.Vmax1or10TF;
1558        GUICommentAddTF = Env.CommentAddTF;
1559        GUIPMWActivateTF = Env.UsePMWTF;
1560        
1561        /* integer values */
1562        GUIDomainID = Env.DomainID;
1563        GUIForecastType = Env.ForecastFileType;
1564        GUIMWJulianDate = Env.MWJulianDate;
1565        GUIMWHHMMSSTime = Env.MWHHMMSSTime;
1566        GUIStartDate = Env.StartJulianDate;
1567        GUIStartTime = Env.StartHHMMSSTime;
1568        GUIEndDate = Env.EndJulianDate;
1569        GUIEndTime = Env.EndHHMMSSTime;
1570        GUIHistoryListFormat = Env.HistoryListFormat;
1571        /* double values */
1572        GUIRawTValue = Env.InitRawTValue;
1573        GUIMWScore = Env.MWScore;
1574        GUICKZGaleRadius = Env.CKZGaleRadius;
1575        GUICKZPenv = Env.CKZPenv;
1576        GUIRMWSize = Env.RMWSize;
1577        GUIUserLatitude = Env.SelectedLatitude;
1578        GUIUserLongitude = Env.SelectedLongitude;
1579        
1580        GUIForecastFileName = Env.ForecastFileName;            // needed?
1581        GUIHistoryFileListingName = Env.ASCIIOutputFileName;   // needed?
1582        GUIATCFStormID = Env.StormIDString;
1583        GUIATCFSiteID = Env.ATCFSourceAgcyIDString;
1584
1585    }
1586
1587    private void loadADTenvParameters() {
1588        /* Env GlobalVariables = new Env(); */
1589        
1590        logger.debug("setting env parameters");
1591        
1592        // send ADT Environmental parameters to Env prior to running ADT
1593        // boolean values
1594        Env.DeleteTF = GUIDeleteTF;
1595        Env.AutoTF = GUIRunAutoTF;
1596        Env.OverTF = GUIOverrideTF;
1597        Env.ATCFOutputTF = GUIATCFOutputTF;
1598        Env.ATCFRecordOutputTF = GUIATCFRecordOutputTF;
1599        Env.InitStrengthTF = GUIInitStrengthTF;
1600        Env.LandFlagTF = GUILandFlagTF;
1601        Env.UseCKZTF = GUIUseCKZTF;
1602        Env.Vmax1or10TF = GUIVmax1or10TF;
1603        Env.CommentAddTF = GUICommentAddTF;
1604        Env.OverSceneTF = GUIOverrideSceneTF;
1605        Env.UsePMWTF = GUIPMWActivateTF;
1606
1607        // integer values
1608        Env.DomainID = GUIDomainID;
1609        Env.ForecastFileType = GUIForecastType;
1610        Env.MWJulianDate = GUIMWJulianDate;
1611        Env.MWHHMMSSTime = GUIMWHHMMSSTime;
1612        Env.StartJulianDate = GUIStartDate;
1613        Env.StartHHMMSSTime = GUIStartTime;
1614        Env.EndJulianDate = GUIEndDate;
1615        Env.EndHHMMSSTime = GUIEndTime;
1616        Env.HistoryListFormat = GUIHistoryListFormat;
1617        // double values
1618        Env.InitRawTValue = GUIRawTValue;
1619        Env.MWScore = GUIMWScore;
1620        Env.CKZGaleRadius = GUICKZGaleRadius;
1621        Env.CKZPenv = GUICKZPenv;
1622        Env.RMWSize = GUIRMWSize;
1623        Env.SelectedLatitude = GUIUserLatitude;
1624        Env.SelectedLongitude = GUIUserLongitude;
1625        
1626        logger.debug("load forecast file name={}", GUIForecastFileName);
1627        Env.ForecastFileName = GUIForecastFileName;   // needed?
1628        Env.ASCIIOutputFileName = GUIHistoryFileListingName;   // needed?
1629        Env.StormIDString = GUIATCFStormID;
1630        Env.ATCFSourceAgcyIDString = GUIATCFSiteID;
1631        
1632    }
1633    
1634    private int ReadGUIOverrideInputFile(String GUIOverrideFile) {
1635        
1636        logger.debug("opening file '{}'", GUIOverrideFile);
1637        
1638        File GUIDataFile = new File(GUIOverrideFile);
1639        String delims = "[ ]+";
1640        String line;
1641        int retval = 1;
1642        
1643        GUIOverrideTF = false;
1644        GUIOverrideSceneTF = false;
1645        GUICommentString = null;
1646        GUIRunAutoTF = true;
1647        GUIDeleteTF = false;
1648        GUICommentAddTF = false;
1649        GUIStartDate = 1900001;
1650        GUIStartTime = 000000;
1651        GUIEndDate = 1900001;
1652        GUIEndTime = 000000;
1653        GUIUserLatitude = -99.5;
1654        GUIUserLongitude = -999.5;
1655        GUIDomainID = 0;
1656        runFullADTAnalysis = true;
1657        
1658        try {
1659            Scanner GUIFile = new Scanner(GUIDataFile);
1660            while (GUIFile.hasNextLine()) {
1661                if ((line = GUIFile.nextLine()).isEmpty()){
1662                    break;
1663                } else {
1664                    String[] tokens = line.split(delims);
1665                    String IDstring = tokens[0];
1666                    String RecValue = tokens[1];
1667                    /* System.out.println("scanning IDstring=%s\n",IDstring); */
1668                    switch (IDstring) {
1669                        case "ATCFOutputTF":
1670                            GUIATCFOutputTF = Boolean.valueOf(RecValue);
1671                            break;
1672                        case "ATCFRecordOutputTF":
1673                            GUIATCFRecordOutputTF = Boolean.valueOf(RecValue);
1674                            break;
1675                        case "InitStrengthTF":
1676                            GUIInitStrengthTF = Boolean.valueOf(RecValue);
1677                            break;
1678                        case "LandFlagTF":
1679                            GUILandFlagTF = Boolean.valueOf(RecValue);
1680                            break;
1681                        case "UseCKZTF":
1682                            GUIUseCKZTF = Boolean.valueOf(RecValue);
1683                            break;
1684                        case "Vmax1or10TF":
1685                            GUIVmax1or10TF = Boolean.valueOf(RecValue);
1686                            break;
1687                        case "UsePMWTF":
1688                            GUIPMWActivateTF = Boolean.valueOf(RecValue);
1689                            break;
1690                        case "ForecastType":
1691                            GUIForecastType = Integer.valueOf(RecValue);
1692                            break;
1693                        case "MWJulianDate":
1694                            GUIMWJulianDate = Integer.valueOf(RecValue);
1695                            break;
1696                        case "MWHHMMSSTime":
1697                            GUIMWHHMMSSTime = Integer.valueOf(RecValue);
1698                            break;
1699                        case "HistoryListFormat":
1700                            GUIHistoryListFormat = Integer.valueOf(RecValue);
1701                            break;
1702                        case "RawTValue":
1703                            GUIRawTValue = Double.valueOf(RecValue);
1704                            break;
1705                        case "MWScore":
1706                            GUIMWScore = Double.valueOf(RecValue);
1707                            break;
1708                        case "CKZGaleRadius":
1709                            GUICKZGaleRadius = Double.valueOf(RecValue);
1710                            break;
1711                        case "CKZPenv":
1712                            GUICKZPenv = Double.valueOf(RecValue);
1713                            break;
1714                        case "RMWSize":
1715                            GUIRMWSize = Double.valueOf(RecValue);
1716                            break;
1717                        case "HistoryFileName":
1718                            GUIHistoryFileName = RecValue;
1719                            break;
1720                        case "ForecastFileName":
1721                            GUIForecastFileName = RecValue;
1722                            break;
1723                        case "HistoryFileListingName":
1724                            GUIHistoryFileListingName = RecValue;
1725                            break;
1726                        case "ATCFStormID":
1727                            GUIATCFStormID = RecValue;
1728                            break;
1729                        case "ATCFSiteID":
1730                            GUIATCFSiteID = RecValue;
1731                            break;
1732                        default:
1733                            break;
1734                    }
1735                }
1736            }
1737            GUIFile.close();
1738        } catch (IOException ex) {
1739            retval = -1;
1740        }
1741        return retval;
1742    }
1743    
1744    public void latLonWidgetChanged() {
1745        logger.debug("latlonwidgetchanged called");
1746        try {
1747            logger.debug("latlon widget changed");
1748            String message = latLonWidget.isValidValues();
1749            if (message != null) {
1750                    userMessage(message);
1751                    return;
1752            }
1753            probeLocation = ucar.visad.Util.makeEarthLocation(
1754                            latLonWidget.getLat(), latLonWidget.getLon()).getLatLonPoint();
1755        } catch (Exception e) {
1756            logException("Handling LatLonWidget changed", e);
1757        }
1758    }
1759    
1760    protected boolean shouldAddDisplayListener() {
1761        return true;
1762    }
1763    
1764    protected boolean shouldAddControlListener() {
1765        return true;
1766    }
1767
1768    protected boolean canHandleEvents() {
1769        if (!getHaveInitialized() || (getMakeWindow() && !getWindowVisible())) {
1770            return false;
1771        }
1772        return isGuiShown();
1773    }
1774    
1775    public void handleDisplayChanged(DisplayEvent event) {
1776        super.handleDisplayChanged(event);
1777        if (canHandleEvents()) {
1778//            int id = event.getId();
1779//            // String idstring = event.toString();
1780//            // InputEvent inputEvent = event.getInputEvent();
1781//            // System.out.println("event ID=%d %s\n",id,idstring);
1782//            try {
1783//                if (id == DisplayEvent.MOUSE_PRESSED_LEFT) {
1784//                    logger.debug("Manual Position Selection");
1785//                    probeLocation = toEarth(event).getLatLonPoint();
1786//                    updateProbeLocation();
1787//                }
1788//            } catch (Exception e) {
1789//                logException("Error selecting position with mouse", e);
1790//            }
1791        }
1792    }
1793    
1794    /**
1795     * Respond to the probe being dragged.
1796     * 
1797     * @param event Event to handle.
1798     */
1799    @Override public void propertyChange(PropertyChangeEvent event) {
1800        if (canHandleEvents() && SelectorDisplayable.PROPERTY_POSITION.equals(event.getPropertyName())) {
1801            try {
1802                RealTuple position = probe.getPosition();
1803                double[] loc = position.getValues();
1804                logger.debug("Manual Position Selection loc={}", loc);
1805                // note: loc[1] is apparently latitude, and loc[0] is longitude!
1806                probeLocation = 
1807                    makeEarthLocation(loc[1], loc[0], loc[2]).getLatLonPoint();
1808                SwingUtilities.invokeLater(this::updatePositionWidget);
1809            } catch (VisADException | RemoteException ex) {
1810                logger.error("Error updating probe location", ex);
1811            }
1812        } else {
1813            super.propertyChange(event);
1814        }
1815    }
1816    
1817    /**
1818     * Update {@link #latLonWidget} if it exists.
1819     * 
1820     * <p>Note: must be called from the event dispatch thread.</p>
1821     */
1822    private void updatePositionWidget() {
1823        if (latLonWidget != null) {
1824            try {
1825                logger.trace("attempting to update widget! lat={} lon={}", probeLocation.getLatitude(), probeLocation.getLongitude());
1826                latLonWidget.setLat(getDisplayConventions().formatLatLon(probeLocation.getLatitude().getValue(CommonUnit.degree)));
1827                latLonWidget.setLon(getDisplayConventions().formatLatLon(probeLocation.getLongitude().getValue(CommonUnit.degree)));
1828            } catch (VisADException ex) {
1829                logger.error("Error updating GUI with probe position", ex);
1830            }
1831        } else {
1832            logger.trace("no lat/lon widget to update!");
1833        }
1834    }
1835    
1836    private void updateProbeLocation() {
1837        try {
1838            if (probeLocation == null) {
1839                return;
1840            }
1841            double lon = probeLocation.getLongitude().getValue(CommonUnit.degree);
1842            double lat = probeLocation.getLatitude().getValue(CommonUnit.degree);
1843            probe.setPosition(
1844                new RealTuple(RealTupleType.SpatialEarth3DTuple, new double[] { lon, lat, 0 }));
1845            probe.setVisible(true);
1846            
1847            GUIUserLatitude = lat;    // added TLO
1848            GUIUserLongitude = lon;    // added TLO
1849            logger.debug("set lat/lon from probe at lat={} lon={}", GUIUserLatitude, GUIUserLongitude);
1850            if (latLonWidget != null) {
1851                latLonWidget.setLat(getDisplayConventions().formatLatLon(
1852                                probeLocation.getLatitude().getValue(CommonUnit.degree)));
1853                latLonWidget.setLon(getDisplayConventions().formatLatLon(
1854                                probeLocation.getLongitude().getValue(CommonUnit.degree)));
1855            }
1856        } catch (Exception e) {
1857            logException("Handling probe changed", e);
1858        }
1859    }
1860
1861    /**
1862     * Set the ProbeLocation property.
1863     *
1864     * @param value New value for ProbeLocation.
1865     */
1866    public void setProbeLocation(LatLonPoint value) {
1867        probeLocation = value;
1868    }
1869
1870    /**
1871     * Get the ProbeLocation property.
1872     *
1873     * @return The ProbeLocation
1874     */
1875    public LatLonPoint getProbeLocation() {
1876            return probeLocation;
1877    }
1878        
1879    protected FlatField getFlatField(FieldImpl data)
1880        throws VisADException, RemoteException
1881    {
1882        FlatField ff;
1883        if (GridUtil.isSequence(data)) {
1884            ff = (FlatField)data.getSample(0);
1885        } else {
1886            ff = (FlatField)data;
1887        }
1888        return ff;
1889    }
1890
1891    public EarthLocation toEarth(DisplayEvent event)
1892        throws VisADException, RemoteException
1893    {
1894        NavigatedDisplay d = getNavigatedDisplay();
1895        return (d == null) ? null : d.getEarthLocation(toBox(event));
1896    }
1897
1898    private void GetImageDateTime() {
1899        
1900        RealTuple timeTuple;
1901        Real tt;
1902        DateTime dat;
1903        
1904        List infos = getDisplayInfos();
1905        DisplayInfo displayInfo = (DisplayInfo) infos.get(0);
1906        
1907        try {
1908            Animation anime = displayInfo.getViewManager().getAnimation();
1909            Set timeSet = anime.getSet();
1910            int pos = anime.getCurrent();
1911            
1912            timeTuple = DataUtility.getSample(timeSet, pos);
1913            tt = (Real) timeTuple.getComponent(0);
1914            dat = new DateTime(tt);
1915        } catch (VisADException e) {
1916            logException("Handling data", e);
1917            return;
1918        } catch (RemoteException f) {
1919            logger.warn("Something went wrong!", f);
1920            return;
1921        }
1922        
1923        double curdate = dat.getValue();
1924        logger.debug("curdate={}",curdate);
1925        
1926        Date datevalue = new Date((long)curdate*1000);
1927        
1928        SimpleDateFormat dateformat = new SimpleDateFormat("yyyyDDD");
1929        SimpleDateFormat timeformat = new SimpleDateFormat("HHmmss");
1930        dateformat.setTimeZone(TimeZone.getTimeZone("GMT"));
1931        timeformat.setTimeZone(TimeZone.getTimeZone("GMT"));
1932        
1933        String JulianDate = dateformat.format(datevalue);
1934        String HHMMSSTime = timeformat.format(datevalue);
1935        int ImageDateInt = Integer.valueOf(JulianDate);
1936        int ImageTimeInt = Integer.valueOf(HHMMSSTime);
1937        // System.out.println("image date = %d  image time=%d\n",ImageDateInt,ImageTimeInt); */
1938        
1939        Data.IRData_JulianDate = ImageDateInt;
1940        Data.IRData_HHMMSSTime = ImageTimeInt;
1941        
1942        logger.debug("IMAGE DATE={} TIME={}", Data.IRData_JulianDate, Data.IRData_HHMMSSTime);
1943    }
1944    
1945    private void GetImageData(float CenterLatitude, float CenterLongitude) {
1946        logger.debug("creating ReadIRImage()...");
1947        
1948        // ReadIRImage IRImage = new ReadIRImage();
1949        
1950        FlatField ffield;
1951        int SatelliteID;
1952        int channel;
1953        
1954        List sources = new ArrayList();
1955        
1956        logger.debug("entering getimagedata");
1957        boolean isTemp = false;
1958        choice.getDataSources(sources);
1959        try {
1960            List infos = getDisplayInfos();
1961            DataInstance de = getDataInstance();
1962            DisplayInfo displayInfo = (DisplayInfo) infos.get(0);
1963                
1964            Animation anime = displayInfo.getViewManager().getAnimation();
1965            // Set timeSet = anime.getSet();
1966            int pos = anime.getCurrent();
1967            ffield = DataUtil.getFlatField(de.getData());
1968            DataSourceImpl dsi = (DataSourceImpl) sources.get(0);
1969            
1970            if (dsi instanceof AddeImageDataSource) {
1971                ImageDataSource dds = (ImageDataSource) sources.get(0);
1972                List imageLists = dds.getImageList();
1973                
1974                AddeImageDescriptor aid = (AddeImageDescriptor) imageLists.get(pos);
1975                AreaDirectory ad = aid.getDirectory();
1976                SatelliteID = ad.getSensorID();
1977                int[] bands = ad.getBands();
1978                channel = bands[0];
1979                
1980                isTemp = Util.isCompatible(ffield, AirTemperature.getRealType());
1981            } else {
1982                channel = 4;
1983                SatelliteID = 70;
1984                // String name = ffield.getSample(0).getType().prettyString();
1985            }
1986        } catch (VisADException e) {
1987            logException("Handling data", e);
1988            return;
1989        } catch (RemoteException f) {
1990            logger.warn("Something went wrong!", f);
1991            return;
1992        }
1993            
1994        // String shortName = choice.getName();
1995        
1996        Env.UserDefineDomain = 0; // automated
1997        // String sidName = Functions.adt_sattypes(SatelliteID);
1998        
1999        logger.debug("SatelliteID={}", SatelliteID);
2000        
2001        try {
2002            ReadIRImage.ReadIRDataFile(ffield,
2003                                       CenterLatitude,
2004                                       CenterLongitude,
2005                                       SatelliteID,
2006                                       channel,
2007                                       isTemp);
2008        }
2009        catch (Exception ex) {
2010            logger.error("ReadIRImage failed", ex);
2011        }
2012    }
2013}