001    /*
002     * $Id: StormTrackControl.java,v 1.1 2012/01/04 20:39:38 tommyj Exp $
003     *
004     * This file is part of McIDAS-V
005     *
006     * Copyright 2007-2012
007     * Space Science and Engineering Center (SSEC)
008     * University of Wisconsin - Madison
009     * 1225 W. Dayton Street, Madison, WI 53706, USA
010     * https://www.ssec.wisc.edu/mcidas
011     * 
012     * All Rights Reserved
013     * 
014     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
015     * some McIDAS-V source code is based on IDV and VisAD source code.  
016     * 
017     * McIDAS-V is free software; you can redistribute it and/or modify
018     * it under the terms of the GNU Lesser Public License as published by
019     * the Free Software Foundation; either version 3 of the License, or
020     * (at your option) any later version.
021     * 
022     * McIDAS-V is distributed in the hope that it will be useful,
023     * but WITHOUT ANY WARRANTY; without even the implied warranty of
024     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
025     * GNU Lesser Public License for more details.
026     * 
027     * You should have received a copy of the GNU Lesser Public License
028     * along with this program.  If not, see http://www.gnu.org/licenses.
029     */
030    
031    package edu.wisc.ssec.mcidasv.control.cyclone;
032    
033    import java.awt.Color;
034    import java.awt.Component;
035    import java.awt.Container;
036    import java.awt.Dimension;
037    import java.awt.Point;
038    import java.awt.Window;
039    import java.awt.event.ActionEvent;
040    import java.awt.event.ActionListener;
041    import java.awt.event.InputEvent;
042    import java.beans.PropertyChangeEvent;
043    import java.io.File;
044    import java.io.FileOutputStream;
045    import java.rmi.RemoteException;
046    import java.text.SimpleDateFormat;
047    import java.util.ArrayList;
048    import java.util.Calendar;
049    import java.util.Date;
050    import java.util.Enumeration;
051    import java.util.GregorianCalendar;
052    import java.util.HashMap;
053    import java.util.Hashtable;
054    import java.util.List;
055    import java.util.Vector;
056    
057    import javax.swing.BorderFactory;
058    import javax.swing.ButtonGroup;
059    import javax.swing.ImageIcon;
060    import javax.swing.JCheckBox;
061    import javax.swing.JComboBox;
062    import javax.swing.JComponent;
063    import javax.swing.JLabel;
064    import javax.swing.JMenu;
065    import javax.swing.JRadioButton;
066    import javax.swing.JScrollPane;
067    import javax.swing.JTabbedPane;
068    import javax.swing.JWindow;
069    import javax.swing.border.BevelBorder;
070    
071    import org.w3c.dom.Element;
072    
073    import ucar.unidata.data.BadDataException;
074    import ucar.unidata.data.DataChoice;
075    import ucar.unidata.data.gis.KmlUtil;
076    import ucar.unidata.data.grid.GridUtil;
077    import ucar.unidata.data.point.PointOb;
078    import ucar.unidata.data.point.PointObFactory;
079    import ucar.unidata.data.storm.StormDataSource;
080    import ucar.unidata.data.storm.StormInfo;
081    import ucar.unidata.data.storm.StormParam;
082    import ucar.unidata.data.storm.StormTrack;
083    import ucar.unidata.data.storm.StormTrackCollection;
084    import ucar.unidata.data.storm.StormTrackPoint;
085    import ucar.unidata.data.storm.Way;
086    import ucar.unidata.geoloc.LatLonRect;
087    import ucar.unidata.idv.MapViewManager;
088    import ucar.unidata.idv.control.DisplayControlImpl;
089    import ucar.unidata.idv.control.ReadoutInfo;
090    import ucar.unidata.ui.TreePanel;
091    import ucar.unidata.ui.TwoListPanel;
092    import ucar.unidata.ui.symbol.StationModel;
093    import ucar.unidata.ui.symbol.StationModelManager;
094    import ucar.unidata.util.ColorTable;
095    import ucar.unidata.util.DateUtil;
096    import ucar.unidata.util.FileManager;
097    import ucar.unidata.util.GuiUtils;
098    import ucar.unidata.util.IOUtil;
099    import ucar.unidata.util.MenuUtil;
100    import ucar.unidata.util.Misc;
101    import ucar.unidata.util.Range;
102    import ucar.unidata.util.StringUtil;
103    import ucar.unidata.util.TwoFacedObject;
104    import ucar.unidata.view.geoloc.NavigatedDisplay;
105    import ucar.unidata.xml.XmlUtil;
106    import ucar.visad.Util;
107    import ucar.visad.display.CompositeDisplayable;
108    import ucar.visad.display.DisplayMaster;
109    import ucar.visad.display.StationModelDisplayable;
110    import visad.CoordinateSystem;
111    import visad.Data;
112    import visad.DateTime;
113    import visad.DisplayEvent;
114    import visad.DoubleSet;
115    import visad.FieldImpl;
116    import visad.FlatField;
117    import visad.FunctionType;
118    import visad.GriddedSet;
119    import visad.Real;
120    import visad.RealType;
121    import visad.Set;
122    import visad.SetType;
123    import visad.TextType;
124    import visad.Tuple;
125    import visad.Unit;
126    import visad.VisADException;
127    import visad.georef.EarthLocation;
128    import visad.georef.MapProjection;
129    
130    /**
131     * A MetApps Display Control with Displayable and controls for displaying a
132     * track (balloon sounding or aircraft track)
133     * 
134     * @author Unidata Development Team
135     * @version $Revision: 1.1 $
136     */
137    
138    public class StormTrackControl extends DisplayControlImpl {
139    
140            /** _more_ */
141            private final static String PREF_STORMDISPLAYSTATE = "pref.stormtrackcontrol.stormdisplaystate";
142    
143            /** _more_ */
144            private final static String PREF_OKWAYS = "pref.stormtrackcontrol.okways";
145    
146            /** _more_ */
147            private final static String PREF_OBWAY = "pref.stormtrackcontrol.observationway";
148    
149            /** _more_ */
150            private final static String PREF_OKPARAMS = "pref.stormtrackcontrol.okparams";
151    
152            /** _more_ */
153            private static int cnt = 0;
154    
155            /** _more_ */
156            final ImageIcon ICON_ON = GuiUtils
157                            .getImageIcon("/ucar/unidata/idv/control/storm/dot.gif");
158    
159            /** _more_ */
160            final ImageIcon ICON_OFF = GuiUtils
161                            .getImageIcon("/ucar/unidata/idv/control/storm/blank.gif");
162    
163            /** _more_ */
164            private StormDisplayState localStormDisplayState;
165    
166            /** _more_ */
167            private Hashtable preferences;
168    
169            /** _more_ */
170            private Hashtable<String, Boolean> okWays;
171    
172            /** _more_ */
173            private Way observationWay;
174    
175            /** _more_ */
176            private Hashtable<String, Boolean> okParams;
177    
178            /** _more_ */
179            private String startTime;
180    
181            /** _more_ */
182            private String endTime;
183    
184            /** _more_ */
185            private CompositeDisplayable placeHolder;
186    
187            /** _more_ */
188            private StormDataSource stormDataSource;
189    
190            /** _more_ */
191            private List<StormInfo> stormInfos;
192    
193            /** Holds the EarthLocation of the last point clicked */
194            private EarthLocation lastEarthLocation = null;
195    
196            /** _more_ */
197            private Hashtable<StormInfo, StormDisplayState> stormDisplayStateMap = new Hashtable<StormInfo, StormDisplayState>();
198    
199            /** _more_ */
200            private List<StormDisplayState> activeStorms;
201    
202            /** _more_ */
203            private TreePanel treePanel;
204    
205            /** _more_ */
206            private static final int YEAR_TIME_MODE_YEAR = 0;
207    
208            /** _more_ */
209            private static final int YEAR_TIME_MODE_STORM = 1;
210    
211            /** _more_ */
212            private int yearTimeMode = YEAR_TIME_MODE_YEAR;
213    
214            /** _more_ */
215            private Hashtable<Integer, YearDisplayState> yearDisplayStateMap = new Hashtable<Integer, YearDisplayState>();
216    
217            /** _more_ */
218            private Hashtable yearData = new Hashtable();
219    
220            /** _more_ */
221            private JComboBox timeModeBox;
222    
223            /** _more_ */
224            private JCheckBox obsCbx;
225    
226            /** _more_ */
227            private JCheckBox forecastCbx;
228    
229            /** _more_ */
230            private JCheckBox mostRecentCbx;
231    
232            /** _more_ */
233            private JCheckBox editedCbx;
234    
235            /** _more_ */
236            private TwoListPanel waysToUseSelector;
237    
238            /** _more_ */
239            private TwoListPanel chartParamsSelector;
240    
241            /** _more_ */
242            private JCheckBox waysToUsePreferenceCbx;
243    
244            /** _more_ */
245            private JCheckBox chartParamsPreferenceCbx;
246    
247            /** _more_ */
248            private List<Way> allWays;
249    
250            /** _more_ */
251            private List<Way> useWays;
252    
253            /** _more_ */
254            private List<StormParam> allParams;
255    
256            /** _more_ */
257            private List<StormParam> useParams;
258    
259            /** _more_ */
260            private JCheckBox obsWayPreferenceCbx;
261    
262            /** _more_ */
263            private List<JRadioButton> obsWayRadioButtons;
264    
265            /** _more_ */
266            private boolean editMode = false;
267    
268            /**
269             * Create a new Track Control; set the attribute flags
270             */
271            public StormTrackControl() {
272                    setAttributeFlags(FLAG_COLORTABLE);
273            }
274    
275            /**
276             * _more_
277             * 
278             * @param basePref
279             *            _more_
280             * 
281             * @return _more_
282             */
283            protected String getPref(String basePref) {
284                    return basePref + "." + stormDataSource.getId();
285            }
286    
287            /**
288             * _more_
289             * 
290             * @return _more_
291             */
292            protected boolean isEditable() {
293                    return stormDataSource.isEditable();
294            }
295    
296            /**
297             * _more_
298             * 
299             * @return _more_
300             */
301            public NavigatedDisplay getVM() {
302                    return getNavigatedDisplay();
303            }
304    
305            /**
306             * Call to help make this kind of Display Control; also calls code to made
307             * the Displayable (empty of data thus far). This method is called from
308             * inside DisplayControlImpl.init(several args).
309             * 
310             * @param dataChoice
311             *            the DataChoice of the moment.
312             * 
313             * @return true if successful
314             * 
315             * @throws RemoteException
316             *             Java RMI error
317             * @throws VisADException
318             *             VisAD Error
319             */
320            public boolean init(DataChoice dataChoice) throws VisADException,
321                            RemoteException {
322    
323                    DataChoice.addCurrentName(new TwoFacedObject(
324                                    "Storm Track>Forecast Hour", "fhour"));
325                    DataChoice.addCurrentName(new TwoFacedObject(
326                                    "Storm Track>Forecast Time", "rhour"));
327                    DataChoice.addCurrentName(new TwoFacedObject(
328                                    "Storm Track>Forecast STI Time", "shour"));
329    
330                    placeHolder = new CompositeDisplayable("Place holder");
331                    addDisplayable(placeHolder);
332    
333                    List dataSources = new ArrayList();
334                    dataChoice.getDataSources(dataSources);
335    
336                    if (dataSources.size() != 1) {
337                            userMessage("Could not find Storm Data Source");
338                            return false;
339                    }
340    
341                    if (!(dataSources.get(0) instanceof StormDataSource)) {
342                            userMessage("Could not find Storm Data Source");
343                            return false;
344                    }
345    
346                    getColorTableWidget(new Range(1.0, 1.0));
347                    stormDataSource = (StormDataSource) dataSources.get(0);
348    
349                    if (okWays == null) {
350                            okWays = (Hashtable<String, Boolean>) getPreferences().get(
351                                            getPref(PREF_OKWAYS));
352                    }
353                    if (observationWay == null) {
354                            observationWay = (Way) getPreferences().get(getPref(PREF_OBWAY));
355                            if (observationWay == null) {
356                                    observationWay = stormDataSource.getDefaultObservationWay();
357                            }
358                    }
359                    if (okWays == null) {
360                            okWays = new Hashtable<String, Boolean>();
361                    }
362                    if (okParams == null) {
363                            okParams = (Hashtable<String, Boolean>) getPreferences().get(
364                                            getPref(PREF_OKPARAMS));
365                    }
366                    if (okParams == null) {
367                            okParams = new Hashtable<String, Boolean>();
368                    }
369    
370                    return true;
371            }
372    
373            /**
374             * _more_
375             * 
376             * @return _more_
377             */
378            private JComponent getWaysToUseComp() {
379    
380                    useWays = new ArrayList<Way>();
381                    allWays = new ArrayList<Way>();
382                    for (Way way : stormDataSource.getWays()) {
383                            if (way.isObservation()) {
384                                    continue;
385                            }
386                            allWays.add(way);
387                            if (okToShowWay(way)) {
388                                    useWays.add(way);
389                            }
390                    }
391                    useWays = (List<Way>) Misc.sort(useWays);
392                    allWays = (List<Way>) Misc.sort(allWays);
393                    if (waysToUsePreferenceCbx == null) {
394                            waysToUsePreferenceCbx = new JCheckBox("Save as preference", false);
395                    }
396                    waysToUseSelector = new TwoListPanel(allWays, "Don't Use", useWays,
397                                    "Use", null, false);
398                    JComponent contents = GuiUtils.centerBottom(waysToUseSelector, GuiUtils
399                                    .left(waysToUsePreferenceCbx));
400    
401                    return contents;
402            }
403    
404            /**
405             * _more_
406             * 
407             * @return _more_
408             */
409            private boolean applyWaysToUse() {
410                    boolean changed = false;
411                    List only = Misc.sort(waysToUseSelector.getCurrentEntries());
412                    if (!useWays.equals(only)) {
413                            changed = true;
414                            if (only.size() == allWays.size()) {
415                                    onlyShowTheseWays(new ArrayList<Way>(), waysToUsePreferenceCbx
416                                                    .isSelected());
417                            } else {
418                                    onlyShowTheseWays((List<Way>) only, waysToUsePreferenceCbx
419                                                    .isSelected());
420                            }
421                    }
422                    return changed;
423            }
424    
425            /**
426             * _more_
427             */
428            public void showWaysToUseDialog() {
429                    JComponent waysToUseComp = getWaysToUseComp();
430                    JLabel label = GuiUtils.cLabel(getWaysName() + " to use");
431                    JComponent contents = GuiUtils.topCenter(label, waysToUseComp);
432                    if (!GuiUtils.showOkCancelDialog(null, getWaysName() + " to use",
433                                    waysToUseComp, null)) {
434                            return;
435                    }
436                    if (applyWaysToUse()) {
437                            // ??
438                    }
439            }
440    
441            /**
442             * _more_
443             * 
444             * @param jtp
445             *            _more_
446             */
447            protected void addPropertiesComponents(JTabbedPane jtp) {
448                    super.addPropertiesComponents(jtp);
449                    JComponent waysToUseComp = getWaysToUseComp();
450                    jtp.add(getWaysName() + " to use", waysToUseComp);
451    
452                    // chart parameters selector
453                    useParams = new ArrayList<StormParam>();
454                    allParams = new ArrayList<StormParam>();
455                    for (StormParam param : getTrackParams()) {
456    
457                            allParams.add(param);
458                            if (okToShowParam(param)) {
459                                    useParams.add(param);
460                            }
461                    }
462                    // useParams = (List<StormParam>) Misc.sort(useParams);
463                    // allParams = (List<StormParam>) Misc.sort(allParams);
464    
465                    if (chartParamsPreferenceCbx == null) {
466                            chartParamsPreferenceCbx = new JCheckBox("Save as preference",
467                                            false);
468                    }
469                    chartParamsSelector = new TwoListPanel(allParams, "All Parameters",
470                                    useParams, "Selected Parameters", null, false);
471                    JComponent paramsContents = GuiUtils.centerBottom(chartParamsSelector,
472                                    GuiUtils.left(chartParamsPreferenceCbx));
473                    jtp.add("Chart Parameters", paramsContents);
474    
475                    // observation way selector
476                    if (stormDataSource.getIsObservationWayChangeable()) {
477                            obsWayRadioButtons = new ArrayList<JRadioButton>();
478                            ButtonGroup bg = new ButtonGroup();
479                            for (Way way : allWays) {
480                                    if (way.isObservation()) {
481                                            continue;
482                                    }
483                                    JRadioButton jrb = new JRadioButton(way.getId(), Misc.equals(
484                                                    observationWay, way));
485                                    obsWayRadioButtons.add(jrb);
486                                    bg.add(jrb);
487                            }
488    
489                            if (obsWayPreferenceCbx == null) {
490                                    obsWayPreferenceCbx = new JCheckBox("Save as preference", false);
491                            }
492    
493                            JComponent obsWayContents = GuiUtils.topLeft(GuiUtils.doLayout(
494                                            obsWayRadioButtons, ((obsWayRadioButtons.size() > 10) ? 2
495                                                            : 1), GuiUtils.WT_N, GuiUtils.WT_N));
496                            int width = 200;
497                            int height = 150;
498                            if (obsWayRadioButtons.size() > 10) {
499                                    obsWayContents = GuiUtils.makeScrollPane(obsWayContents, width,
500                                                    height);
501                            }
502                            jtp.add("Observation " + getWayName(), GuiUtils.centerBottom(
503                                            obsWayContents, GuiUtils.left(obsWayPreferenceCbx)));
504                    }
505            }
506    
507            /**
508             * _more_
509             * 
510             * @return _more_
511             */
512            public List<StormParam> getTrackParams() {
513                    List<StormParam> params = new ArrayList<StormParam>();
514    
515                    StormDisplayState sds = getCurrentStormDisplayState();
516                    if (sds == null) {
517                            return params;
518                    }
519    
520                    StormTrackCollection stc = sds.getTrackCollection();
521                    if (stc == null) {
522                            for (int i = stormInfos.size() - 1; i >= 0; i--) {
523                                    StormInfo stormInfo = stormInfos.get(i);
524                                    StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
525                                    stc = sds.getTrackCollection();
526                                    if (stc != null) {
527                                            break;
528                                    }
529                            }
530                    }
531    
532                    if (stc == null) {
533                            System.err.println("Unable to find any active storm displays");
534                            return params;
535                    }
536                    for (StormTrack track : stc.getTracks()) {
537                            if (track == null) {
538                                    continue;
539                            }
540                            if (!track.isObservation()) {
541                                    params = track.getParams();
542                                    break;
543                            }
544                    }
545    
546                    // If we didn't get any from the forecast track use the obs track
547                    if (params.size() == 0) {
548                            StormTrack obsTrack = stc.getObsTrack();
549                            if (obsTrack != null) {
550                                    params = obsTrack.getParams();
551                            }
552                    }
553    
554                    return params;
555            }
556    
557            /**
558             * _more_
559             * 
560             * @return _more_
561             */
562            public boolean doApplyProperties() {
563                    if (!super.doApplyProperties()) {
564                            return false;
565                    }
566    
567                    boolean changed = false;
568                    if (applyWaysToUse()) {
569                            changed = true;
570                    }
571    
572                    List onlyCP = chartParamsSelector.getCurrentEntries();
573                    if (!useParams.equals(onlyCP)) {
574                            changed = true;
575                            if (onlyCP.size() == allParams.size()) {
576                                    onlyShowTheseParams(new ArrayList<StormParam>(),
577                                                    chartParamsPreferenceCbx.isSelected());
578                            } else {
579                                    onlyShowTheseParams((List<StormParam>) onlyCP,
580                                                    chartParamsPreferenceCbx.isSelected());
581                            }
582                    }
583    
584                    if (stormDataSource.getIsObservationWayChangeable()) {
585                            Way newObsWay = null;
586                            for (int i = 0; i < obsWayRadioButtons.size(); i++) {
587                                    if (obsWayRadioButtons.get(i).isSelected()) {
588                                            newObsWay = allWays.get(i);
589                                            break;
590                                    }
591                            }
592    
593                            if (newObsWay != null) {
594                                    if (!Misc.equals(newObsWay, observationWay)) {
595                                            changed = true;
596                                            observationWay = newObsWay;
597                                    }
598                                    if (obsWayPreferenceCbx.isSelected()) {
599                                            putPreference(getPref(PREF_OBWAY), observationWay);
600                                    }
601                            }
602                    }
603    
604                    if (changed) {
605                            reloadStormTracks();
606                    }
607    
608                    return true;
609            }
610    
611            /*
612             * public List<StormParam> getChartParamFromSelector(){
613             * if(chartParamsSelector!= null) { List pa =
614             * chartParamsSelector.getCurrentEntries(); return pa; } return null; }
615             */
616    
617            /**
618             * Signal base class to add this as a control listener
619             * 
620             * @return Add as control listener
621             */
622            protected boolean shouldAddControlListener() {
623                    return true;
624            }
625    
626            /** locking object */
627            private Object MUTEX = new Object();
628    
629            /**
630             * _more_
631             */
632            public void viewpointChanged() {
633                    super.viewpointChanged();
634                    synchronized (MUTEX) {
635                            StormDisplayState sds = getCurrentStormDisplayState();
636                            HashMap<Way, List> wayToTracksMap = sds.getTrackCollection()
637                                            .getWayToTracksHashMap();
638                            // Way obsWay = new Way(Way.OBSERVATION);
639                            java.util.Set<Way> ways = wayToTracksMap.keySet();
640    
641                            for (Way way : ways) {
642    
643                                    if (way.equals(Way.OBSERVATION)) {
644                                            WayDisplayState obsWDS = sds.getWayDisplayState(way);
645                                            try {
646                                                    obsWDS.updateLayoutModel();
647                                            } catch (Exception exc) {
648                                                    logException("view point Changed", exc);
649                                                    return;
650                                            }
651                                    }
652    
653                            }
654    
655                            /*
656                             * if ( !getHaveInitialized() || !getActive()) { return; }
657                             * Rectangle2D newBounds = calculateRectangle(); boolean
658                             * shouldReload = false; if ((lastViewBounds == null) ||
659                             * (lastViewBounds.getWidth() == 0) || (lastViewBounds.getHeight()
660                             * == 0)) { shouldReload = true; } else if (
661                             * !(newBounds.equals(lastViewBounds))) { double widthratio =
662                             * newBounds.getWidth() / lastViewBounds.getWidth(); double
663                             * heightratio = newBounds.getHeight() / lastViewBounds.getHeight();
664                             * double xdiff = Math.abs(newBounds.getX() -
665                             * lastViewBounds.getX()); double ydiff = Math.abs(newBounds.getY()
666                             * - lastViewBounds.getY()); // See if this is 20% greater or
667                             * smaller than before. if ((((widthratio < .80) || (widthratio >
668                             * 1.20)) && ((heightratio < .80) || (heightratio > 1.20))) ||
669                             * ((xdiff > .2 * lastViewBounds.getWidth()) || (ydiff > .2 *
670                             * lastViewBounds.getHeight()))) { shouldReload = true; } } float
671                             * newScale = getScaleFromDisplayable(); if
672                             * (Float.floatToIntBits(lastViewScale) !=
673                             * Float.floatToIntBits(newScale)) { shouldReload = true; } if
674                             * (shouldReload) {
675                             * 
676                             * updateLayoutModel();
677                             * 
678                             * }
679                             */
680                    }
681    
682            }
683    
684            /** _more_ */
685            private Hashtable rangeTypes = new Hashtable();
686    
687            /**
688             * _more_
689             * 
690             * @param track
691             *            _more_
692             * 
693             * @param param
694             *            _more_
695             * 
696             * @return _more_
697             * 
698             * @throws Exception
699             *             _more_
700             */
701            protected FieldImpl makeTrackField(StormTrack track, StormParam param)
702                            throws Exception {
703    
704                    List<StormTrackPoint> points = track.getTrackPoints();
705                    int numPoints = points.size();
706                    RealType rangeType = null;
707                    double[][] newRangeVals = new double[1][numPoints];
708                    float[] alts = new float[numPoints];
709                    float[] lats = new float[numPoints];
710                    float[] lons = new float[numPoints];
711                    Real[] values = ((param == null) ? null : track
712                                    .getTrackAttributeValues(param));
713                    Unit unit = ((param != null) ? param.getUnit() : null);
714                    for (int pointIdx = 0; pointIdx < numPoints; pointIdx++) {
715                            StormTrackPoint stp = points.get(pointIdx);
716                            Real value = ((values == null) ? null : values[pointIdx]);
717    
718                            // Set the dflt so we can use its unit later
719                            if (rangeType == null) {
720                                    String key = track.getWay() + "_" + track.getId() + "_" + param;
721                                    if (track.getWay().toString().startsWith("Observation_year"))
722                                            key = track.getWay() + "_" + param;
723                                    rangeType = (RealType) rangeTypes.get(key);
724                                    if (rangeType == null) {
725                                            cnt++;
726                                            if (track.getWay().toString()
727                                                            .startsWith("Observation_year"))
728                                                    rangeType = Util.makeRealType("trackrange_"
729                                                                    + track.getWay() + "_" + cnt, unit);
730                                            else
731                                                    rangeType = Util.makeRealType("trackrange_"
732                                                                    + track.getId() + "_" + track.getWay() + "_"
733                                                                    + cnt, unit);
734                                            rangeTypes.put(key, rangeType);
735                                    }
736                            }
737                            EarthLocation el = stp.getLocation();
738                            newRangeVals[0][pointIdx] = ((value != null) ? value.getValue() : 0);
739                            lats[pointIdx] = (float) el.getLatitude().getValue();
740                            lons[pointIdx] = (float) el.getLongitude().getValue();
741                            alts[pointIdx] = 1;
742                            // if(Math.abs(lats[i])>90) System.err.println("bad lat:" +
743                            // lats[i]);
744                    }
745                    GriddedSet llaSet = ucar.visad.Util
746                                    .makeEarthDomainSet(lats, lons, alts);
747                    Set[] rangeSets = new Set[] { new DoubleSet(new SetType(rangeType)) };
748                    FunctionType newType = new FunctionType(((SetType) llaSet.getType())
749                                    .getDomain(), rangeType);
750                    FlatField trackField = new FlatField(newType, llaSet,
751                                    (CoordinateSystem) null, rangeSets, new Unit[] { unit });
752                    trackField.setSamples(newRangeVals, false);
753                    return trackField;
754            }
755    
756            /**
757             * _more_
758             * 
759             * @param whichColorTable
760             *            _more_
761             * @param newColorTable
762             *            _more_
763             * 
764             * @throws RemoteException
765             *             _more_
766             * @throws VisADException
767             *             _more_
768             */
769            public void setColorTable(String whichColorTable, ColorTable newColorTable)
770                            throws RemoteException, VisADException {
771                    super.setColorTable(whichColorTable, newColorTable);
772                    for (StormDisplayState sds : getActiveStorms()) {
773                            sds.colorTableChanged();
774                    }
775            }
776    
777            /**
778             * _more_
779             * 
780             * @return _more_
781             */
782            public DisplayMaster getDisplayMaster() {
783                    return getDisplayMaster(placeHolder);
784            }
785    
786            /**
787             * _more_
788             * 
789             * @param way
790             *            _more_
791             * 
792             * @return _more_
793             */
794            protected boolean okToShowWay(Way way) {
795    
796                    if (way.isObservation()) {
797                            return true;
798                    }
799                    if (okWays == null) {
800                            showWaysToUseDialog();
801                    }
802                    if (okWays == null) {
803                            return true;
804                    }
805                    if ((okWays.size() > 0) && (okWays.get(way.getId()) == null)) {
806                            return false;
807                    }
808                    return true;
809            }
810    
811            /**
812             * _more_
813             * 
814             * @param param
815             *            _more_
816             * 
817             * @return _more_
818             */
819            protected boolean okToShowParam(StormParam param) {
820                    if (okParams == null) {
821                            return true;
822                    }
823                    if ((okParams.size() > 0) && (okParams.get(param.getName()) == null)) {
824                            return false;
825                    }
826                    return true;
827            }
828    
829            /**
830             * _more_
831             * 
832             * @return _more_
833             */
834            public StormDisplayState getCurrentStormDisplayState() {
835                    if (localStormDisplayState != null) {
836                            return localStormDisplayState;
837                    }
838                    if (treePanel == null) {
839                            return null;
840                    }
841    
842                    Component comp = treePanel.getVisibleComponent();
843                    if (comp == null) {
844                            return null;
845                    }
846                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
847                            StormInfo stormInfo = stormInfos.get(i);
848                            StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
849                            if (stormDisplayState.getContents() == comp) {
850                                    return stormDisplayState;
851                            }
852                    }
853                    return null;
854            }
855    
856            /**
857             * This gets called when the control has received notification of a
858             * dataChange event. In this class, it reloads the storm tracks.
859             * 
860             * @throws RemoteException
861             *             Java RMI problem
862             * @throws VisADException
863             *             VisAD problem
864             */
865            protected void resetData() throws VisADException, RemoteException {
866                    reloadStormTracks();
867            }
868    
869            /**
870             * _more_
871             * 
872             * @return _more_
873             */
874            private List<StormDisplayState> getStormDisplays() {
875                    List<StormDisplayState> states = new ArrayList<StormDisplayState>();
876                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
877                            StormInfo stormInfo = stormInfos.get(i);
878                            states.add(getStormDisplayState(stormInfo));
879                    }
880                    return states;
881            }
882    
883            /**
884             * _more_
885             */
886            private void reloadStormTracks() {
887                    for (StormDisplayState stormDisplayState : getActiveStorms()) {
888                            stormDisplayState.reload();
889                    }
890            }
891    
892            /**
893             * _more_
894             * 
895             * @param ways
896             *            _more_
897             * @param writeAsPreference
898             *            _more_
899             */
900            private void onlyShowTheseWays(List<Way> ways, boolean writeAsPreference) {
901                    okWays = new Hashtable();
902                    for (Way way : ways) {
903                            okWays.put(way.getId(), new Boolean(true));
904                    }
905                    if (writeAsPreference) {
906                            putPreference(getPref(PREF_OKWAYS), okWays);
907                    }
908    
909            }
910    
911            /**
912             * _more_
913             * 
914             * @param params
915             *            _more_
916             * @param writeAsPreference
917             *            _more_
918             */
919            private void onlyShowTheseParams(List<StormParam> params,
920                            boolean writeAsPreference) {
921                    okParams = new Hashtable();
922                    for (StormParam param : params) {
923                            okParams.put(param.getName(), new Boolean(true));
924                    }
925                    if (writeAsPreference) {
926                            putPreference(getPref(PREF_OKPARAMS), okParams);
927                    }
928    
929            }
930    
931            /**
932             * _more_
933             * 
934             * @return _more_
935             */
936            public StormDataSource getStormDataSource() {
937                    return stormDataSource;
938            }
939    
940            /**
941             * _more_
942             * 
943             * @param stormDisplayState
944             *            _more_
945             */
946            public void viewStorm(StormDisplayState stormDisplayState) {
947                    if (treePanel != null) {
948                            treePanel.show(stormDisplayState.getContents());
949                    }
950            }
951    
952            /**
953             * _more_
954             */
955            public void unloadAllTracks() {
956                    for (StormDisplayState stormDisplayState : getActiveStorms()) {
957                            stormDisplayState.deactivate();
958                    }
959            }
960    
961            /**
962             * _more_
963             * 
964             * @return _more_
965             */
966            protected boolean canHandleEvents() {
967                    if (!editMode || !getHaveInitialized()
968                                    || (getMakeWindow() && !getWindowVisible())) {
969                            return false;
970                    }
971                    return isGuiShown();
972            }
973    
974            /**
975             * _more_
976             * 
977             * @param event
978             *            _more_
979             */
980            public void handleDisplayChanged(DisplayEvent event) {
981    
982                    StormDisplayState current = getCurrentStormDisplayState();
983                    if ((current == null) || !current.getActive()) {
984                            return;
985                    }
986                    int id = event.getId();
987                    if (id == DisplayEvent.MOUSE_MOVED) {
988                            return;
989                    }
990                    if (!canHandleEvents()) {
991                            return;
992                    }
993                    InputEvent inputEvent = event.getInputEvent();
994                    try {
995                            current.handleEvent(event);
996                    } catch (Exception exc) {
997                            logException("Error handling edit", exc);
998                    }
999            }
1000    
1001            /**
1002             * _more_
1003             * 
1004             * @param items
1005             *            _more_
1006             * @param forMenuBar
1007             *            _more_
1008             */
1009            protected void getSaveMenuItems(List items, boolean forMenuBar) {
1010                    StormDisplayState current = getCurrentStormDisplayState();
1011                    if ((current != null) && current.getActive()) {
1012                            items.add(GuiUtils.makeMenuItem("Save Storm Display as Preference",
1013                                            this, "saveStormDisplayState"));
1014    
1015                            if (getPreferences().get(getPref(PREF_STORMDISPLAYSTATE)) != null) {
1016                                    items.add(GuiUtils.makeMenuItem(
1017                                                    "Remove Storm Display Preference", this,
1018                                                    "deleteStormDisplayState"));
1019                            }
1020                            items.add(GuiUtils.MENU_SEPARATOR);
1021                            items.add(GuiUtils.makeMenuItem("Export to Data File", current,
1022                                            "writeToDataFile"));
1023    
1024                    }
1025                    items.add(GuiUtils.makeMenuItem("Export to Google Earth", this,
1026                                    "writeToKml"));
1027                    super.getSaveMenuItems(items, forMenuBar);
1028            }
1029    
1030            /**
1031             * _more_
1032             * 
1033             * @param items
1034             *            _more_
1035             * @param forMenuBar
1036             *            _more_
1037             */
1038            protected void getEditMenuItems(List items, boolean forMenuBar) {
1039                    items.add(MenuUtil.makeCheckboxMenuItem("Edit Mode", this, "editMode",
1040                                    null));
1041    
1042                    StormDisplayState current = getCurrentStormDisplayState();
1043                    if ((current != null) && current.getActive()) {
1044                            items.add(GuiUtils.makeMenuItem("Add Forecast Time Chart", current,
1045                                            "addForecastTimeChart"));
1046                            items.add(GuiUtils.makeMenuItem("Add Forecast Hour Chart", current,
1047                                            "addForecastHourChart"));
1048                    }
1049                    super.getEditMenuItems(items, forMenuBar);
1050            }
1051    
1052            /**
1053             * _more_
1054             * 
1055             * @param items
1056             *            _more_
1057             * @param forMenuBar
1058             *            _more_
1059             */
1060            protected void getViewMenuItems(List items, boolean forMenuBar) {
1061                    try {
1062                            List subMenus = new ArrayList();
1063                            GregorianCalendar cal = new GregorianCalendar(DateUtil.TIMEZONE_GMT);
1064                            Hashtable menus = new Hashtable();
1065                            List activeItems = new ArrayList();
1066                            for (int i = stormInfos.size() - 1; i >= 0; i--) {
1067                                    StormInfo stormInfo = stormInfos.get(i);
1068                                    cal.setTime(ucar.visad.Util.makeDate(stormInfo.getStartTime()));
1069                                    int year = cal.get(Calendar.YEAR);
1070                                    JMenu yearMenu = (JMenu) menus.get("" + year);
1071                                    if (yearMenu == null) {
1072                                            yearMenu = new JMenu("" + year);
1073                                            menus.put("" + year, yearMenu);
1074                                            subMenus.add(yearMenu);
1075                                    }
1076                                    StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
1077                                    if (stormDisplayState.getActive()) {
1078                                            activeItems.add(MenuUtil.makeMenuItem(stormInfo.toString(),
1079                                                            this, "viewStorm", stormDisplayState));
1080                                    }
1081                                    if (stormInfo.getBasin() != null) {
1082                                            JMenu basinMenu = (JMenu) menus.get(year + "Basin:"
1083                                                            + stormInfo.getBasin());
1084                                            if (basinMenu == null) {
1085                                                    basinMenu = new JMenu("Basin:" + stormInfo.getBasin());
1086                                                    menus.put(year + "Basin:" + stormInfo.getBasin(),
1087                                                                    basinMenu);
1088                                                    yearMenu.add(basinMenu);
1089                                            }
1090                                            yearMenu = basinMenu;
1091                                    }
1092                                    yearMenu.add(GuiUtils.makeMenuItem(stormInfo.toString(), this,
1093                                                    "viewStorm", stormDisplayState));
1094                            }
1095    
1096                            JMenu trackMenu = GuiUtils.makeMenu("Storm Tracks", subMenus);
1097                            GuiUtils.limitMenuSize(trackMenu, "Tracks:", 30);
1098    
1099                            if (activeItems.size() > 0) {
1100                                    activeItems.add(0, GuiUtils.MENU_SEPARATOR);
1101                                    activeItems.add(0, GuiUtils.makeMenuItem("Unload all tracks",
1102                                                    this, "unloadAllTracks", null));
1103                                    trackMenu.insert(GuiUtils
1104                                                    .makeMenu("Active Tracks", activeItems), 0);
1105                            }
1106    
1107                            items.add(trackMenu);
1108                            super.getViewMenuItems(items, forMenuBar);
1109                    } catch (Exception exc) {
1110                            logException("Making track menu", exc);
1111                    }
1112            }
1113    
1114            /**
1115             * _more_
1116             * 
1117             * @return _more_
1118             */
1119            public String getWayName() {
1120                    return stormDataSource.getWayName();
1121            }
1122    
1123            /**
1124             * _more_
1125             * 
1126             * @return _more_
1127             */
1128            public String getWaysName() {
1129                    return stormDataSource.getWaysName();
1130            }
1131    
1132            /**
1133             * _more_
1134             * 
1135             * @return _more_
1136             */
1137            protected String getDataProjectionLabel() {
1138                    return "Use Projection From Tracks";
1139            }
1140    
1141            /**
1142             * _more_
1143             * 
1144             * @return _more_
1145             */
1146            public MapProjection getDataProjection() {
1147                    return null;
1148            }
1149    
1150            /**
1151             * _more_
1152             * 
1153             * @return _more_
1154             */
1155            public boolean hasMapProjection() {
1156                    return true;
1157            }
1158    
1159            /**
1160             * _more_
1161             * 
1162             * @return _more_
1163             */
1164            public MapProjection getDataProjectionForMenu() {
1165                    try {
1166                            double minLon = Double.POSITIVE_INFINITY;
1167                            double maxLon = Double.NEGATIVE_INFINITY;
1168                            double minLat = Double.POSITIVE_INFINITY;
1169                            double maxLat = Double.NEGATIVE_INFINITY;
1170                            List<StormDisplayState> stormDisplayStates = getStormDisplayStates();
1171                            boolean didone = false;
1172                            for (StormDisplayState stormDisplayState : getActiveStorms()) {
1173                                    LatLonRect bbox = stormDisplayState.getBoundingBox();
1174                                    if (bbox == null) {
1175                                            continue;
1176                                    }
1177                                    minLon = Math.min(minLon, bbox.getLonMin());
1178                                    maxLon = Math.max(maxLon, bbox.getLonMax());
1179                                    minLat = Math.min(minLat, bbox.getLatMin());
1180                                    maxLat = Math.max(maxLat, bbox.getLatMax());
1181                                    didone = true;
1182                            }
1183    
1184                            for (YearDisplayState yearDisplayState : getYearDisplayStates()) {
1185                                    if (!yearDisplayState.getActive()) {
1186                                            continue;
1187                                    }
1188                                    List<StormTrack> yearTracks = yearDisplayState.getStormTracks();
1189                                    for (StormTrack track : yearTracks) {
1190                                            LatLonRect bbox = track.getBoundingBox();
1191                                            if (bbox == null) {
1192                                                    continue;
1193                                            }
1194                                            minLon = Math.min(minLon, bbox.getLonMin());
1195                                            maxLon = Math.max(maxLon, bbox.getLonMax());
1196                                            minLat = Math.min(minLat, bbox.getLatMin());
1197                                            maxLat = Math.max(maxLat, bbox.getLatMax());
1198                                            didone = true;
1199                                    }
1200                            }
1201    
1202                            if (!didone) {
1203                                    return null;
1204                            }
1205                            return ucar.visad.Util.makeMapProjection(minLat, minLon, maxLat,
1206                                            maxLon);
1207                    } catch (Exception exc) {
1208                            logException("Error making projection from tracks", exc);
1209                            return null;
1210                    }
1211    
1212            }
1213    
1214            /**
1215             * _more_
1216             * 
1217             * @return _more_
1218             */
1219            private List<StormDisplayState> getActiveStorms() {
1220                    if (activeStorms == null) {
1221                            List<StormDisplayState> tmpList = new ArrayList<StormDisplayState>();
1222                            List<StormDisplayState> stormDisplayStates = getStormDisplayStates();
1223                            for (StormDisplayState stormDisplayState : stormDisplayStates) {
1224                                    if (stormDisplayState.getActive()) {
1225                                            tmpList.add(stormDisplayState);
1226                                    }
1227                            }
1228                            activeStorms = tmpList;
1229                    }
1230                    return activeStorms;
1231            }
1232    
1233            /**
1234             * _more_
1235             * 
1236             * @return _more_
1237             */
1238            private Hashtable getPreferences() {
1239                    if (preferences == null) {
1240                            String path = stormDataSource.getClass().getName()
1241                                            + ".StormTrackControl.xml";
1242                            preferences = (Hashtable) getIdv().getStore().getEncodedFile(path);
1243                            if (preferences == null) {
1244                                    preferences = new Hashtable();
1245                            }
1246                    }
1247                    return preferences;
1248            }
1249    
1250            /**
1251             * _more_
1252             */
1253            public void deleteStormDisplayState() {
1254                    String template = (String) getPreferences().get(
1255                                    getPref(PREF_STORMDISPLAYSTATE));
1256                    if (template != null) {
1257                            getPreferences().remove(getPref(PREF_STORMDISPLAYSTATE));
1258                            writePreferences();
1259                    }
1260            }
1261    
1262            /**
1263             * _more_
1264             */
1265            public void saveStormDisplayState() {
1266                    try {
1267                            StormDisplayState current = getCurrentStormDisplayState();
1268                            if (current == null) {
1269                                    return;
1270                            }
1271                            boolean wasActive = current.getActive();
1272                            current.setActive(false);
1273                            current.setStormTrackControl(null);
1274                            String xml = getIdv().encodeObject(current, false);
1275                            current.setStormTrackControl(this);
1276                            current.setActive(wasActive);
1277                            putPreference(getPref(PREF_STORMDISPLAYSTATE), xml);
1278                            userMessage("<html>Preference saved. <br>Note: This will take effect for new display controls</html>");
1279                    } catch (Exception exc) {
1280                            logException("Saving storm display", exc);
1281                    }
1282    
1283            }
1284    
1285            /**
1286             * _more_
1287             */
1288            private void writePreferences() {
1289                    String path = stormDataSource.getClass().getName()
1290                                    + ".StormTrackControl.xml";
1291                    getIdv().getStore().putEncodedFile(path, preferences);
1292            }
1293    
1294            /**
1295             * _more_
1296             * 
1297             * @param key
1298             *            _more_
1299             * @param object
1300             *            _more_
1301             */
1302            private void putPreference(String key, Object object) {
1303                    getPreferences().put(key, object);
1304                    writePreferences();
1305            }
1306    
1307            /**
1308             * _more_
1309             * 
1310             * @param stormInfo
1311             *            _more_
1312             * 
1313             * @return _more_
1314             */
1315            private StormDisplayState getStormDisplayState(StormInfo stormInfo) {
1316                    StormDisplayState stormDisplayState = stormDisplayStateMap
1317                                    .get(stormInfo);
1318                    try {
1319                            if (stormDisplayState == null) {
1320                                    String template = (String) getPreferences().get(
1321                                                    getPref(PREF_STORMDISPLAYSTATE));
1322                                    if (template != null) {
1323                                            try {
1324                                                    stormDisplayState = (StormDisplayState) getIdv()
1325                                                                    .decodeObject(template);
1326                                                    stormDisplayState.setStormInfo(stormInfo);
1327                                            } catch (Exception exc) {
1328                                                    logException("Creating storm display", exc);
1329                                                    System.err.println("Error decoding preference:" + exc);
1330                                                    // noop
1331                                            }
1332                                    }
1333                            }
1334                            if (stormDisplayState == null) {
1335                                    stormDisplayState = new StormDisplayState(stormInfo);
1336                            }
1337    
1338                            stormDisplayState.setStormTrackControl(this);
1339                            stormDisplayStateMap.put(stormInfo, stormDisplayState);
1340                    } catch (Exception exc) {
1341                            logException("Creating storm display", exc);
1342                    }
1343    
1344                    return stormDisplayState;
1345            }
1346    
1347            /**
1348             * _more_
1349             */
1350            public void initDone() {
1351                    super.initDone();
1352                    try {
1353                            for (Enumeration keys = stormDisplayStateMap.keys(); keys
1354                                            .hasMoreElements();) {
1355                                    StormInfo key = (StormInfo) keys.nextElement();
1356                                    StormDisplayState stormDisplayState = stormDisplayStateMap
1357                                                    .get(key);
1358                                    stormDisplayState.setStormTrackControl(this);
1359                                    stormDisplayState.initDone();
1360    
1361                                    MapProjection mapProjection = getDataProjectionForMenu();
1362                                    if (mapProjection != null) {
1363                                            MapViewManager mvm = getMapViewManager();
1364                                            if (mvm != null) {
1365                                                    mvm.setMapProjection(mapProjection, true,
1366                                                                    getDisplayConventions().getMapProjectionLabel(
1367                                                                                    mapProjection, this), true);
1368                                            }
1369                                    }
1370    
1371                            }
1372                    } catch (Exception exc) {
1373                            logException("Setting new storm info", exc);
1374                    }
1375                    Misc.run(this, "initYears");
1376                    getControlContext().getStationModelManager().addPropertyChangeListener(
1377                                    this);
1378            }
1379    
1380            /**
1381             * _more_
1382             * 
1383             * @throws RemoteException
1384             *             _more_
1385             * @throws VisADException
1386             *             _more_
1387             */
1388            public void doRemove() throws VisADException, RemoteException {
1389                    getControlContext().getStationModelManager()
1390                                    .removePropertyChangeListener(this);
1391                    super.doRemove();
1392            }
1393    
1394            /**
1395             * _more_
1396             */
1397            public void initYears() {
1398                    List<YearDisplayState> ydss = getYearDisplayStates();
1399                    for (YearDisplayState yds : ydss) {
1400                            if (!yds.getActive()) {
1401                                    continue;
1402                            }
1403                            try {
1404                                    yds.setState(yds.STATE_LOADING);
1405                                    loadYearInner(yds);
1406                            } catch (Exception exc) {
1407                                    logException("Loading year", exc);
1408                                    return;
1409                            }
1410                    }
1411                    loadYearPointData();
1412            }
1413    
1414            /** _more_ */
1415            private StationModelDisplayable yearLabels;
1416    
1417            /**
1418             * _more_
1419             */
1420            private void loadYearPointData() {
1421                    try {
1422                            if (yearLabels == null) {
1423                                    yearLabels = new StationModelDisplayable("storm year labels");
1424                                    yearLabels.setScale(getDisplayScale());
1425                                    StationModelManager smm = getControlContext()
1426                                                    .getStationModelManager();
1427                                    StationModel model = smm.getStationModel("Label");
1428                                    yearLabels.setStationModel(model);
1429                                    addDisplayable(yearLabels);
1430                            }
1431    
1432                            List allPointObs = new ArrayList();
1433                            List<YearDisplayState> ydss = getYearDisplayStates();
1434                            for (YearDisplayState yds : ydss) {
1435                                    if (!yds.getActive()) {
1436                                            continue;
1437                                    }
1438                                    List tmp = yds.getPointObs();
1439                                    if (tmp != null) {
1440                                            allPointObs.addAll(tmp);
1441                                    }
1442                            }
1443    
1444                            if (allPointObs.size() == 0) {
1445                                    removeDisplayable(yearLabels);
1446                                    yearLabels = null;
1447                            } else {
1448                                    yearLabels.setStationData(PointObFactory
1449                                                    .makeTimeSequenceOfPointObs(allPointObs, -1, -1));
1450                            }
1451                    } catch (Exception exc) {
1452                            logException("Loading year", exc);
1453                    }
1454            }
1455    
1456            /**
1457             * _more_
1458             * 
1459             * @param yds
1460             *            _more_
1461             */
1462            public void unloadYear(final YearDisplayState yds) {
1463                    Misc.run(new Runnable() {
1464                            public void run() {
1465                                    try {
1466                                            loadYearPointData();
1467                                    } catch (Exception exc) {
1468                                            logException("Loading year", exc);
1469                                    }
1470                            }
1471                    });
1472            }
1473    
1474            /**
1475             * _more_
1476             * 
1477             * @param yds
1478             *            _more_
1479             */
1480            public void loadYear(final YearDisplayState yds) {
1481                    Misc.run(new Runnable() {
1482                            public void run() {
1483                                    try {
1484                                            yds.setState(yds.STATE_LOADING);
1485                                            loadYearInner(yds);
1486                                            loadYearPointData();
1487                                    } catch (Exception exc) {
1488                                            logException("Loading year", exc);
1489                                    }
1490                            }
1491                    });
1492    
1493            }
1494    
1495            /**
1496             * _more_
1497             * 
1498             * @param yds
1499             *            _more_
1500             * 
1501             * @throws Exception
1502             *             _more_
1503             */
1504            public void loadYearInner(YearDisplayState yds) throws Exception {
1505    
1506                    TextType textType = TextType.getTextType("ID");
1507                    List fields = new ArrayList();
1508                    List times = new ArrayList();
1509                    List<StormTrack> obsTracks = new ArrayList<StormTrack>();
1510                    List<PointOb> pointObs = new ArrayList<PointOb>();
1511    
1512                    JWindow errorWindow = null;
1513                    JLabel errorLabel = null;
1514    
1515                    SimpleDateFormat sdf = new SimpleDateFormat("yyyy");
1516                    sdf.setTimeZone(DateUtil.TIMEZONE_GMT);
1517                    GregorianCalendar cal = new GregorianCalendar(DateUtil.TIMEZONE_GMT);
1518                    Hashtable<String, Boolean> obsWays = new Hashtable<String, Boolean>();
1519                    obsWays.put(Way.OBSERVATION.toString(), new Boolean(true));
1520                    String currentMessage = "";
1521                    String errors = "";
1522                    boolean doYearTime = yearTimeMode == YEAR_TIME_MODE_YEAR;
1523                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
1524                            if (yds.getState() != yds.STATE_LOADING) {
1525                                    yds.setState(YearDisplayState.STATE_INACTIVE);
1526                                    yds.setStatus("");
1527                                    if (errorWindow != null) {
1528                                            errorWindow.setVisible(false);
1529                                    }
1530                                    return;
1531                            }
1532                            StormInfo stormInfo = stormInfos.get(i);
1533                            cal.setTime(ucar.visad.Util.makeDate(stormInfo.getStartTime()));
1534                            int stormYear = cal.get(Calendar.YEAR);
1535                            if (stormYear != yds.getYear()) {
1536                                    continue;
1537                            }
1538    
1539                            Object key = yds.getYear() + "_" + stormInfo.getStormId();
1540                            StormTrack obsTrack = (StormTrack) yearData.get(key);
1541                            if (obsTrack == null) {
1542                                    yds.setStatus("Loading " + stormInfo + "...");
1543                                    currentMessage = "Loading " + stormInfo;
1544                                    try {
1545                                            StormTrackCollection tracks = stormDataSource
1546                                                            .getTrackCollection(stormInfo, obsWays,
1547                                                                            observationWay);
1548                                            obsTrack = tracks.getObsTrack();
1549                                            if (obsTrack == null) {
1550                                                    continue;
1551                                            }
1552                                            obsTrack = new StormTrack(obsTrack);
1553                                            obsTrack.setWay(new Way(obsTrack.getWay() + "_year"
1554                                                            + yds.getYear()));
1555                                            yearData.put(key, obsTrack);
1556                                    } catch (BadDataException bde) {
1557                                            if (errorWindow == null) {
1558                                                    Window parent = GuiUtils.getWindow(yds.getButton());
1559                                                    errorWindow = new JWindow(parent);
1560                                                    errorWindow.getContentPane().add(
1561                                                                    errorLabel = new JLabel(" "));
1562                                                    errorLabel.setBorder(BorderFactory
1563                                                                    .createBevelBorder(BevelBorder.RAISED));
1564                                                    errorWindow.pack();
1565                                                    try {
1566                                                            Point loc = yds.getButton().getLocationOnScreen();
1567                                                            errorWindow.setLocation((int) loc.getX(),
1568                                                                            (int) (loc.getY() + yds.getButton()
1569                                                                                            .bounds().height));
1570    
1571                                                    } catch (Exception exc) {
1572                                                            // Ignore this incase the component isn't being
1573                                                            // shown
1574                                                    }
1575                                                    errorWindow.show();
1576                                            }
1577                                            errors = errors + "Error " + currentMessage + "<br>";
1578                                            yds.setStatus("Error:" + currentMessage);
1579                                            errorLabel.setText("<html><i>" + errors + "</i></html>");
1580                                            errorWindow.pack();
1581                                    }
1582                            }
1583    
1584                            if (obsTrack != null) {
1585                                    FieldImpl field = makeTrackField(obsTrack, null);
1586                                    StormTrackPoint stp = obsTrack.getTrackPoints().get(0);
1587                                    DateTime dttm = new DateTime(sdf.parse("" + yds.getYear()));
1588                                    if (!doYearTime) {
1589                                            dttm = stormInfo.getStartTime();
1590                                    }
1591                                    obsTracks.add(obsTrack);
1592                                    times.add(dttm);
1593                                    fields.add(field);
1594                                    Tuple tuple = new Tuple(new Data[] { new visad.Text(textType,
1595                                                    stormInfo.toString()) });
1596                                    pointObs.add(PointObFactory.makePointOb(stp.getLocation(),
1597                                                    dttm, tuple));
1598                            }
1599                    }
1600                    if (errorWindow != null) {
1601                            errorWindow.setVisible(false);
1602                    }
1603                    // If we can't find an obs track then set the yds to be inactive
1604                    if (times.size() == 0) {
1605                            yds.setStatus("No observation track found");
1606                            yds.setState(YearDisplayState.STATE_INACTIVE);
1607                    } else {
1608                            yds.setData(doYearTime, obsTracks, times, fields, pointObs);
1609                            yds.setState(YearDisplayState.STATE_ACTIVE);
1610                            yds.setStatus("");
1611                    }
1612    
1613            }
1614    
1615            /**
1616             * _more_
1617             */
1618            public void writeToKml() {
1619                    if (obsCbx == null) {
1620                            obsCbx = new JCheckBox("Observation", true);
1621                            forecastCbx = new JCheckBox("Forecast", true);
1622                            mostRecentCbx = new JCheckBox("Most Recent Forecasts", false);
1623                    }
1624                    JComponent accessory = GuiUtils.top(GuiUtils.vbox(obsCbx, forecastCbx,
1625                                    mostRecentCbx));
1626    
1627                    String filename = FileManager.getWriteFile(Misc
1628                                    .newList(FileManager.FILTER_KML), FileManager.SUFFIX_KML,
1629                                    accessory);
1630                    if (filename == null) {
1631                            return;
1632                    }
1633    
1634                    try {
1635                            writeToKml(filename, obsCbx.isSelected(), forecastCbx.isSelected(),
1636                                            mostRecentCbx.isSelected());
1637                    } catch (Exception exc) {
1638                            logException("Writing KML", exc);
1639                    }
1640            }
1641    
1642            /**
1643             * _more_
1644             * 
1645             * @param filename
1646             *            _more_
1647             * @param doObs
1648             *            _more_
1649             * @param doForecast
1650             *            _more_
1651             * @param mostRecent
1652             *            _more_
1653             * 
1654             * @throws RemoteException
1655             *             _more_
1656             * @throws VisADException
1657             *             _more_
1658             */
1659            public void writeToKml(String filename, boolean doObs, boolean doForecast,
1660                            boolean mostRecent) throws VisADException, RemoteException {
1661                    try {
1662                            Element kmlNode = KmlUtil.kml("");
1663                            Element docNode = KmlUtil.document(kmlNode, "");
1664                            KmlUtil
1665                                            .iconstyle(docNode, "hurricaneicon",
1666                                                            "http://www.unidata.ucar.edu/software/idv/kml/images/hurricane.png");
1667                            Hashtable state = new Hashtable();
1668                            for (StormDisplayState stormDisplayState : getActiveStorms()) {
1669                                    stormDisplayState.writeToKml(docNode, state, doObs, doForecast,
1670                                                    mostRecent);
1671                            }
1672    
1673                            List<YearDisplayState> ydss = getYearDisplayStates();
1674                            for (YearDisplayState yds : ydss) {
1675                                    if (!yds.getActive()) {
1676                                            continue;
1677                                    }
1678                                    Element yearNode = KmlUtil.folder(docNode, "Year:"
1679                                                    + yds.getYear());
1680                                    for (StormTrack track : yds.getStormTracks()) {
1681                                            writeToGE(docNode, state, yearNode, track, yds.getColor());
1682                                    }
1683                            }
1684    
1685                            FileOutputStream fileOut = new FileOutputStream(filename);
1686                            IOUtil.writeBytes(new File(filename), XmlUtil.toString(kmlNode)
1687                                            .getBytes());
1688    
1689                    } catch (Exception exc) {
1690                            logException("Writing KML", exc);
1691                    }
1692            }
1693    
1694            /**
1695             * _more_
1696             * 
1697             * 
1698             * @param docNode
1699             *            _more_
1700             * @param state
1701             *            _more_
1702             * @param parent
1703             *            _more_
1704             * @param track
1705             *            _more_
1706             * @param color
1707             *            _more_
1708             * 
1709             * 
1710             * @throws RemoteException
1711             *             _more_
1712             * @throws VisADException
1713             *             _more_
1714             * 
1715             * @throws Exception
1716             *             _more_
1717             */
1718            protected void writeToGE(Element docNode, Hashtable state, Element parent,
1719                            StormTrack track, Color color) throws Exception {
1720                    Element placemark = KmlUtil.placemark(parent, "Track", "<html>"
1721                                    + getWayName() + ":" + track.getWay() + "<br>" + ""
1722                                    + track.getStartTime() + "</html>");
1723    
1724                    int cnt = 0;
1725                    String dateString = track.getStartTime().formattedString(
1726                                    "yyyy-MM-dd hhmm", DateUtil.TIMEZONE_GMT);
1727                    String sheetName = track.getWay() + " - " + dateString;
1728                    int rowCnt = 0;
1729                    List<StormParam> params = track.getParams();
1730                    StringBuffer sb = new StringBuffer();
1731                    for (StormTrackPoint stp : track.getTrackPoints()) {
1732                            EarthLocation el = stp.getLocation();
1733                            if (track.getWay().isObservation()) {
1734                                    Element icon = KmlUtil.placemark(parent, "Time:"
1735                                                    + stp.getTime(),
1736                                                    "<html><table>" + formatStormTrackPoint(track, stp)
1737                                                                    + "</table></html>", el.getLatitude().getValue(
1738                                                                    visad.CommonUnit.degree), el.getLongitude()
1739                                                                    .getValue(visad.CommonUnit.degree), (el
1740                                                                    .getAltitude() != null ? el.getAltitude()
1741                                                                    .getValue() : 0), "#hurricaneicon");
1742                                    KmlUtil
1743                                                    .timestamp(icon, ucar.visad.Util
1744                                                                    .makeDate(stp.getTime()));
1745                            }
1746    
1747                            sb.append(el.getLongitude().getValue());
1748                            sb.append(",");
1749                            sb.append(el.getLatitude().getValue());
1750                            sb.append(",");
1751                            sb.append(el.getAltitude().getValue());
1752                            sb.append("\n");
1753                    }
1754    
1755                    String styleUrl = "linestyle" + track.getWay();
1756                    if (state.get(styleUrl) == null) {
1757                            Element style = KmlUtil.linestyle(docNode, styleUrl, color, track
1758                                            .getWay().isObservation() ? 3 : 2);
1759                            state.put(styleUrl, style);
1760                    }
1761                    KmlUtil.styleurl(placemark, "#" + styleUrl);
1762                    Element linestring = KmlUtil.linestring(placemark, false, false, sb
1763                                    .toString());
1764                    // KmlUtil.timestamp(linestring, track.getStartTime());
1765                    if (!track.getWay().isObservation()) {
1766                            KmlUtil.timestamp(placemark, ucar.visad.Util.makeDate(track
1767                                            .getStartTime()));
1768                    } else {
1769                    }
1770            }
1771    
1772            /**
1773             * Make the gui
1774             * 
1775             * @return The gui
1776             * 
1777             * @throws RemoteException
1778             *             On Badness
1779             * @throws VisADException
1780             *             On Badness
1781             */
1782            protected Container doMakeContents() throws VisADException, RemoteException {
1783    
1784                    // Get the storm infos and sort them
1785                    stormInfos = (List<StormInfo>) Misc.sort(stormDataSource
1786                                    .getStormInfos());
1787    
1788                    if (stormInfos.size() == 1) {
1789                            try {
1790                                    if (localStormDisplayState == null) {
1791                                            localStormDisplayState = new StormDisplayState(stormInfos
1792                                                            .get(0));
1793                                    }
1794                                    stormDisplayStateMap = new Hashtable<StormInfo, StormDisplayState>();
1795                                    localStormDisplayState.setStormTrackControl(this);
1796                                    stormDisplayStateMap.put(stormInfos.get(0),
1797                                                    localStormDisplayState);
1798                                    localStormDisplayState.setIsOnlyChild(true);
1799                                    JComponent comp = localStormDisplayState.getContents();
1800                                    localStormDisplayState.loadStorm();
1801                                    return comp;
1802                            } catch (Exception exc) {
1803                                    logException("Creating storm display", exc);
1804                                    return new JLabel("Error");
1805                            }
1806                    }
1807                    localStormDisplayState = null;
1808                    treePanel = new TreePanel(true, 150);
1809                    Hashtable years = new Hashtable();
1810                    JComponent firstComponent = null;
1811                    JComponent firstSelectedComponent = null;
1812                    GregorianCalendar cal = new GregorianCalendar(DateUtil.TIMEZONE_GMT);
1813    
1814                    List yearPanels = new ArrayList();
1815                    List yearComps = new ArrayList();
1816                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
1817                            StormInfo stormInfo = stormInfos.get(i);
1818                            cal.setTime(ucar.visad.Util.makeDate(stormInfo.getStartTime()));
1819                            int year = cal.get(Calendar.YEAR);
1820                            if (years.get(new Integer(year)) == null) {
1821                                    YearDisplayState yds = getYearDisplayState(year);
1822                                    yearComps.add(new JLabel("" + year));
1823                                    yearComps.add(yds.getButton());
1824                                    yearComps.add(GuiUtils.wrap(yds.getColorSwatch()));
1825                                    yearComps.add(yds.getLabel());
1826                                    years.put(new Integer(year), "");
1827                                    if (yearComps.size() > 20) {
1828                                            GuiUtils.tmpInsets = GuiUtils.INSETS_5;
1829                                            yearPanels.add(GuiUtils.doLayout(yearComps, 4,
1830                                                            GuiUtils.WT_NNNY, GuiUtils.WT_N));
1831                                            yearComps = new ArrayList();
1832                                    }
1833                            }
1834                    }
1835                    GuiUtils.tmpInsets = GuiUtils.INSETS_5;
1836                    yearPanels.add(GuiUtils.doLayout(yearComps, 4, GuiUtils.WT_NNNY,
1837                                    GuiUtils.WT_N));
1838    
1839                    JComponent yearComponent = GuiUtils.vbox(yearPanels);
1840                    if (yearPanels.size() > 0) {
1841                            int width = 300;
1842                            int height = 400;
1843                            JScrollPane scroller = GuiUtils.makeScrollPane(GuiUtils
1844                                            .top(yearComponent), width, height);
1845                            scroller.setBorder(BorderFactory.createLoweredBevelBorder());
1846                            scroller.setPreferredSize(new Dimension(width, height));
1847                            scroller.setMinimumSize(new Dimension(width, height));
1848                            yearComponent = scroller;
1849                    }
1850                    timeModeBox = new JComboBox(new Vector(Misc.newList("Start Year",
1851                                    "Storm Date")));
1852                    timeModeBox.setSelectedIndex(yearTimeMode);
1853                    timeModeBox.addActionListener(new ActionListener() {
1854                            public void actionPerformed(ActionEvent ae) {
1855                                    yearTimeMode = timeModeBox.getSelectedIndex();
1856                                    Misc.run(StormTrackControl.this, "initYears");
1857                            }
1858                    });
1859    
1860                    JComponent yearTopComp = GuiUtils.inset(GuiUtils.left(GuiUtils.label(
1861                                    "Time Mode: ", timeModeBox)), 5);
1862    
1863                    treePanel.addComponent(GuiUtils.topCenter(yearTopComp, yearComponent),
1864                                    null, "Yearly Tracks", null);
1865    
1866                    years = new Hashtable();
1867    
1868                    // Go in reverse order so we get the latest first
1869                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
1870                            StormInfo stormInfo = stormInfos.get(i);
1871                            cal.setTime(ucar.visad.Util.makeDate(stormInfo.getStartTime()));
1872                            int year = cal.get(Calendar.YEAR);
1873                            StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
1874    
1875                            String category = "" + year;
1876                            JComponent panelContents = stormDisplayState.getContents();
1877                            if (stormInfo.getBasin() != null) {
1878                                    category = category + TreePanel.CATEGORY_DELIMITER + "Basin:"
1879                                                    + stormInfo.getBasin();
1880                            }
1881                            treePanel.addComponent(panelContents, category, stormInfo
1882                                            .toString(), stormDisplayState.getActive() ? ICON_ON
1883                                            : ICON_OFF);
1884    
1885                            if (stormDisplayState.getActive()
1886                                            && (firstSelectedComponent == null)) {
1887                                    firstSelectedComponent = panelContents;
1888                            }
1889                            if (firstComponent == null) {
1890                                    firstComponent = panelContents;
1891                            }
1892                    }
1893    
1894                    // Show the first selected component or the first component
1895                    if (firstSelectedComponent != null) {
1896                            treePanel.show(firstSelectedComponent);
1897                    } else if (firstComponent != null) {
1898                            treePanel.show(firstComponent);
1899                    }
1900    
1901                    // treePanel.setPreferredSize(new Dimension(500, 400));
1902                    JComponent contents = treePanel;
1903    
1904                    // JComponent contents = GuiUtils.topCenter(GuiUtils.left(box),
1905                    // scroller);
1906                    // contents.setPreferredSize(new Dimension(500, 400));
1907    
1908                    if ((startTime != null) && (endTime != null)) {
1909                            try {
1910    
1911                                    Date[] range = DateUtil.getDateRange(startTime, endTime,
1912                                                    new Date());
1913                                    double fromDate = range[0].getTime();
1914                                    double toDate = range[1].getTime();
1915                                    for (StormInfo stormInfo : stormInfos) {
1916                                            double date = Util.makeDate(stormInfo.getStartTime())
1917                                                            .getTime();
1918                                            StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
1919                                            if ((date >= fromDate) && (date <= toDate)) {
1920                                                    stormDisplayState.loadStorm();
1921                                            } else if (stormDisplayState.getActive()) {
1922                                                    stormDisplayState.deactivate();
1923                                            }
1924                                    }
1925                            } catch (java.text.ParseException pe) {
1926                                    logException("Error parsing start/end dates:" + startTime + " "
1927                                                    + endTime, pe);
1928                            }
1929                    }
1930    
1931                    return contents;
1932            }
1933    
1934            /**
1935             * _more_
1936             * 
1937             * @param stormDisplayState
1938             *            _more_
1939             */
1940            public void stormChanged(StormDisplayState stormDisplayState) {
1941                    activeStorms = null;
1942                    if (treePanel != null) {
1943                            treePanel.setIcon(stormDisplayState.getContents(),
1944                                            stormDisplayState.getActive() ? ICON_ON : ICON_OFF);
1945                    }
1946            }
1947    
1948            /**
1949             * Respond to a timeChange event
1950             * 
1951             * @param time
1952             *            new time
1953             */
1954            protected void timeChanged(Real time) {
1955                    try {
1956                            List<StormDisplayState> active = getActiveStorms();
1957                            for (StormDisplayState stormDisplayState : active) {
1958                                    stormDisplayState.timeChanged(time);
1959                            }
1960                    } catch (Exception exc) {
1961                            logException("changePosition", exc);
1962                    }
1963                    super.timeChanged(time);
1964            }
1965    
1966            /**
1967             * Property change method.
1968             * 
1969             * @param evt
1970             *            event to act on
1971             */
1972            public void propertyChange(PropertyChangeEvent evt) {
1973                    if (evt.getPropertyName().equals(
1974                                    StationModelManager.PROP_RESOURCECHANGE)) {
1975                            StationModel changedModel = (StationModel) evt.getNewValue();
1976                            handleChangedStationModel(changedModel.getName());
1977                    } else if (evt.getPropertyName().equals(
1978                                    StationModelManager.PROP_RESOURCEREMOVE)) {
1979                            StationModel changedModel = (StationModel) evt.getOldValue();
1980                            handleChangedStationModel(changedModel.getName());
1981                    }
1982                    super.propertyChange(evt);
1983            }
1984    
1985            /**
1986             * _more_
1987             * 
1988             * @param name
1989             *            _more_
1990             */
1991            private void handleChangedStationModel(String name) {
1992                    for (int i = stormInfos.size() - 1; i >= 0; i--) {
1993                            StormInfo stormInfo = stormInfos.get(i);
1994                            StormDisplayState stormDisplayState = getStormDisplayState(stormInfo);
1995                            if (stormDisplayState.getActive()) {
1996                                    stormDisplayState.handleChangedStationModel(name);
1997                            }
1998                    }
1999    
2000            }
2001    
2002            /**
2003             * Set the StormDisplayStates property.
2004             * 
2005             * @param value
2006             *            The new value for StormDisplayStates
2007             */
2008            public void setStormDisplayStates(List<StormDisplayState> value) {
2009                    if (value != null) {
2010                            for (StormDisplayState stormDisplayState : value) {
2011                                    stormDisplayStateMap.put(stormDisplayState.getStormInfo(),
2012                                                    stormDisplayState);
2013                            }
2014                    }
2015            }
2016    
2017            /**
2018             * Get the StormDisplayStates property.
2019             * 
2020             * @return The StormDisplayStates
2021             */
2022            public List<StormDisplayState> getStormDisplayStates() {
2023                    List<StormDisplayState> stormDisplayStates = new ArrayList<StormDisplayState>();
2024                    for (Enumeration keys = stormDisplayStateMap.keys(); keys
2025                                    .hasMoreElements();) {
2026                            StormInfo key = (StormInfo) keys.nextElement();
2027                            StormDisplayState stormDisplayState = stormDisplayStateMap.get(key);
2028                            // TODO: We don't want to add every state, just the ones that have
2029                            // been changed
2030                            // if(stormDisplayState.getChanged()) {
2031                            if (stormDisplayState.getActive()) {
2032                                    stormDisplayStates.add(stormDisplayState);
2033                            }
2034                    }
2035                    return stormDisplayStates;
2036            }
2037    
2038            /**
2039             * _more_
2040             * 
2041             * @param year
2042             *            _more_
2043             * 
2044             * @return _more_
2045             */
2046            public YearDisplayState getYearDisplayState(int year) {
2047                    YearDisplayState yearDisplayState = yearDisplayStateMap
2048                                    .get(new Integer(year));
2049                    if (yearDisplayState == null) {
2050                            yearDisplayState = new YearDisplayState(this, year);
2051                            yearDisplayStateMap.put(new Integer(year), yearDisplayState);
2052                    }
2053                    return yearDisplayState;
2054            }
2055    
2056            /**
2057             * Set the YearDisplayStates property.
2058             * 
2059             * @param value
2060             *            The new value for YearDisplayStates
2061             */
2062            public void setYearDisplayStates(List<YearDisplayState> value) {
2063                    if (value != null) {
2064                            yearDisplayStateMap = new Hashtable<Integer, YearDisplayState>();
2065                            for (YearDisplayState yearDisplayState : value) {
2066                                    yearDisplayStateMap.put(
2067                                                    new Integer(yearDisplayState.getYear()),
2068                                                    yearDisplayState);
2069                            }
2070                    }
2071            }
2072    
2073            /**
2074             * Get the YearDisplayStates property.
2075             * 
2076             * @return The YearDisplayStates
2077             */
2078            public List<YearDisplayState> getYearDisplayStates() {
2079                    List<YearDisplayState> yearDisplayStates = new ArrayList<YearDisplayState>();
2080                    for (Enumeration keys = yearDisplayStateMap.keys(); keys
2081                                    .hasMoreElements();) {
2082                            Object key = keys.nextElement();
2083                            YearDisplayState yearDisplayState = yearDisplayStateMap.get(key);
2084                            if (yearDisplayState.getActive()) {
2085                                    yearDisplayStates.add(yearDisplayState);
2086                            }
2087                    }
2088                    return yearDisplayStates;
2089            }
2090    
2091            /**
2092             * _more_
2093             * 
2094             * @param el
2095             *            _more_
2096             * @param animationValue
2097             *            _more_
2098             * @param animationStep
2099             *            _more_
2100             * @param samples
2101             *            _more_
2102             * 
2103             * @return _more_
2104             * 
2105             * @throws Exception
2106             *             _more_
2107             */
2108            protected List getCursorReadoutInner(EarthLocation el, Real animationValue,
2109                            int animationStep, List<ReadoutInfo> samples) throws Exception {
2110    
2111                    StormTrackPoint ob = null;
2112    
2113                    List result = new ArrayList();
2114                    List theStormStates = getStormDisplayStates();
2115                    if (theStormStates != null) {
2116                            Object[] pair = findClosestPoint(el, theStormStates,
2117                                            animationValue, 20);
2118                            if (pair != null) {
2119                                    StormTrack closestTrack = (StormTrack) pair[0];
2120                                    StormTrackPoint closestOb = (StormTrackPoint) pair[1];
2121                                    result.add("<tr><td>" + "Way: " + closestTrack.getWay()
2122                                                    + "</td></tr> "
2123                                                    + formatStormTrackPoint(closestTrack, closestOb));
2124    
2125                            }
2126                    }
2127    
2128                    return result;
2129            }
2130    
2131            /**
2132             * _more_
2133             * 
2134             * 
2135             * @param stormTrack
2136             *            _more_
2137             * @param stp
2138             *            _more_
2139             * 
2140             * @return _more_
2141             * 
2142             * @throws RemoteException
2143             *             _more_
2144             * @throws VisADException
2145             *             _more_
2146             */
2147            protected String formatStormTrackPoint(StormTrack stormTrack,
2148                            StormTrackPoint stp) throws VisADException, RemoteException {
2149                    Unit displayUnit = getDisplayUnit();
2150                    double value;
2151                    if (stp == null) {
2152                            return "";
2153                    }
2154                    List<StormParam> params = stormTrack.getParams();
2155                    // result = "<tr><td>" + "Storm: "
2156                    // + stp.toString() + "</td></tr>";
2157                    String result = "<tr><td>" + "Track Point Time:</td><td align=right>"
2158                                    + stp.getTime() + "</td></tr>";
2159                    for (StormParam param : params) {
2160                            Real r = stp.getAttribute(param);
2161                            if (r == null) {
2162                                    continue;
2163                            }
2164                            Unit unit = param.getUnit();
2165                            result = result + "<tr><td>" + param.toString()
2166                                            + ":</td><td align=right>" + Misc.format(r.getValue())
2167                                            + ((unit != null) ? ("[" + unit + "]") : "") + "</td></tr>";
2168                    }
2169    
2170                    int length = result.length();
2171                    return StringUtil.padLeft(result, 5 * (20 - length), "&nbsp;");
2172            }
2173    
2174            /**
2175             * This finds the StormTrack and StormTrackPoint that is closest to the
2176             * given location
2177             * 
2178             * 
2179             * @param el
2180             *            _more_
2181             * @param theStates
2182             *            _more_
2183             * @param animationValue
2184             *            _more_
2185             * @param distanceThresholdPixels
2186             *            _more_
2187             * @return A 2-tuple. First element is the StormTrack. Second element is the
2188             *         ob. Or null if none found
2189             * 
2190             * @throws Exception
2191             *             _more_
2192             */
2193            protected Object[] findClosestPoint(EarthLocation el,
2194                            List<StormDisplayState> theStates, Real animationValue,
2195                            int distanceThresholdPixels) throws Exception {
2196                    if ((el == null) || (theStates == null)) {
2197                            return null;
2198                    }
2199    
2200                    int numStates = theStates.size();
2201                    StormTrackPoint closestOb = null;
2202                    StormTrack closestTrack = null;
2203    
2204                    int[] clickPt = boxToScreen(earthToBox(el));
2205                    double minDistance = distanceThresholdPixels;
2206                    // System.err.println ("click:" + clickPt[0]+"/"+clickPt[1] + " "
2207                    // +minDistance);
2208    
2209                    for (int i = 0; i < numStates; i++) {
2210                            StormDisplayState sds = theStates.get(i);
2211                            if (sds == null) {
2212                                    continue;
2213                            }
2214                            StormTrackCollection trackCollection = sds.getTrackCollection();
2215                            if (trackCollection == null) {
2216                                    continue;
2217                            }
2218                            StormInfo sinfo = sds.getStormInfo();
2219                            HashMap<Way, List> wayToTracksMap = trackCollection
2220                                            .getWayToTracksHashMap();
2221                            // Way obsWay = new Way(Way.OBSERVATION);
2222                            java.util.Set<Way> ways = wayToTracksMap.keySet();
2223    
2224                            for (Way way : ways) {
2225                                    StormTrack track = null;
2226                                    if (way.equals(Way.OBSERVATION)) {
2227                                            // WayDisplayState trackWDS = wayToTracksMap.get(way);
2228                                            // //get(Way.OBSERVATION);
2229                                            List<StormTrack> tracks = wayToTracksMap.get(way);
2230                                            if (tracks.size() > 0) {
2231                                                    track = tracks.get(0);
2232                                            }
2233                                    } else {
2234                                            WayDisplayState trackWDS = sds.getWayDisplayState(way); // get(Way.OBSERVATION);
2235                                            boolean visible = checkTracksVisible(animationValue,
2236                                                            trackWDS);
2237                                            if (visible) {
2238                                                    List<StormTrack> tracks = wayToTracksMap.get(way);
2239                                                    track = getClosestTimeForecastTrack(tracks,
2240                                                                    animationValue);
2241                                            }
2242                                    }
2243    
2244                                    if (track == null) {
2245                                            continue;
2246                                    }
2247                                    // System.err.println(way + " track time is: " +
2248                                    // track.getStartTime());
2249                                    List<StormTrackPoint> stpList = track.getTrackPoints();
2250                                    int size = stpList.size();
2251                                    for (int j = 0; j < size; j++) {
2252                                            StormTrackPoint stp = stpList.get(j);
2253                                            EarthLocation stpLoc = stp.getLocation();
2254                                            int[] obScreen = boxToScreen(earthToBox(stpLoc));
2255                                            double distance = GuiUtils.distance(obScreen, clickPt);
2256                                            if (distance < minDistance) {
2257                                                    closestOb = stp;
2258                                                    minDistance = distance;
2259                                                    closestTrack = track;
2260                                            }
2261                                    }
2262                            }
2263                            // System.err.println ("\t" + obScreen[0]+"/"+obScreen[1] + " d:" +
2264                            // distance);
2265    
2266                    }
2267    
2268                    if (closestOb != null) {
2269                            return new Object[] { closestTrack, closestOb };
2270                    }
2271    
2272                    return null;
2273            }
2274    
2275            /**
2276             * _more_
2277             * 
2278             * @param currentAnimationTime
2279             *            _more_
2280             * @param wds
2281             *            _more_
2282             * 
2283             * @return _more_
2284             * 
2285             * @throws Exception
2286             *             _more_
2287             */
2288            private boolean checkTracksVisible(Real currentAnimationTime,
2289                            WayDisplayState wds) throws Exception {
2290                    if ((currentAnimationTime == null) || currentAnimationTime.isMissing()) {
2291                            return false;
2292                    }
2293                    // Iterate way display states
2294                    boolean visible = false;
2295                    if (wds.shouldShowTrack() && wds.hasTrackDisplay()) {
2296                            FieldImpl field = (FieldImpl) wds.getTrackDisplay().getData();
2297                            if (field == null) {
2298                                    return false;
2299                            }
2300                            Set timeSet = GridUtil.getTimeSet(field);
2301                            if (timeSet == null) {
2302                                    return false;
2303                            }
2304                            if (timeSet.getLength() == 1) {
2305                                    return true;
2306                            } else {
2307                                    // Else work the visad magic
2308                                    float timeValueFloat = (float) currentAnimationTime
2309                                                    .getValue(timeSet.getSetUnits()[0]);
2310                                    // System.err.println("multiple times:" + timeValueFloat);
2311                                    float[][] value = { { timeValueFloat } };
2312                                    int[] index = timeSet.valueToIndex(value);
2313                                    // System.err.println("index:" + index[0]);
2314                                    return visible = (index[0] >= 0);
2315                            }
2316    
2317                    }
2318                    return visible;
2319            }
2320    
2321            /**
2322             * _more_
2323             * 
2324             * @param tracks
2325             *            _more_
2326             * @param pTime
2327             *            _more_
2328             * 
2329             * @return _more_
2330             * 
2331             * @throws VisADException
2332             *             _more_
2333             */
2334            private StormTrack getClosestTimeForecastTrack(List<StormTrack> tracks,
2335                            Real pTime) throws VisADException {
2336    
2337                    DateTime dt = new DateTime(pTime); // pTime.
2338                    double timeToLookFor = dt.getValue();
2339                    int numPoints = tracks.size();
2340                    double lastTime = -1;
2341    
2342                    // for(StormTrack track: tracks){
2343                    // if(track.getTrackStartTime().equals(dt))
2344                    // return track;
2345                    // }
2346                    for (int i = 0; i < numPoints; i++) {
2347                            StormTrack st = tracks.get(i);
2348                            double currentTime = st.getStartTime().getValue();
2349                            if (timeToLookFor == currentTime) {
2350                                    return st;
2351                            }
2352                            if (timeToLookFor < currentTime) {
2353                                    if (i == 0) {
2354                                            return null;
2355                                    }
2356                                    if (timeToLookFor > lastTime) {
2357                                            return tracks.get(i - 1);
2358                                    }
2359                            }
2360                            lastTime = currentTime;
2361                    }
2362                    return null;
2363            }
2364    
2365            /**
2366             * Set the OkWays property.
2367             * 
2368             * @param value
2369             *            The new value for OkWays
2370             */
2371            public void setOkWays(Hashtable<String, Boolean> value) {
2372                    okWays = value;
2373            }
2374    
2375            /**
2376             * _more_
2377             * 
2378             * @param value
2379             *            _more_
2380             */
2381            public void setObservationWay(Way value) {
2382                    observationWay = value;
2383            }
2384    
2385            /**
2386             * Get the OkWays property.
2387             * 
2388             * @return The OkWays
2389             */
2390            public Hashtable<String, Boolean> getOkWays() {
2391                    return okWays;
2392            }
2393    
2394            /**
2395             * _more_
2396             * 
2397             * @return _more_
2398             */
2399            public Way getObservationWay() {
2400                    return observationWay;
2401            }
2402    
2403            /**
2404             * Set the OkParams property.
2405             * 
2406             * @param value
2407             *            The new value for OkParams
2408             */
2409            public void setOkParams(Hashtable<String, Boolean> value) {
2410                    okParams = value;
2411            }
2412    
2413            /**
2414             * Get the OkParams property.
2415             * 
2416             * @return The OkParams
2417             */
2418            public Hashtable<String, Boolean> getOkParams() {
2419                    return okParams;
2420            }
2421    
2422            /**
2423             * Set the StartTime property.
2424             * 
2425             * @param value
2426             *            The new value for StartTime
2427             */
2428            public void setStartTime(String value) {
2429                    startTime = value;
2430            }
2431    
2432            /**
2433             * Get the StartTime property.
2434             * 
2435             * @return The StartTime
2436             */
2437            public String getStartTime() {
2438                    return startTime;
2439            }
2440    
2441            /**
2442             * Set the EndTime property.
2443             * 
2444             * @param value
2445             *            The new value for EndTime
2446             */
2447            public void setEndTime(String value) {
2448                    endTime = value;
2449            }
2450    
2451            /**
2452             * Get the EndTime property.
2453             * 
2454             * @return The EndTime
2455             */
2456            public String getEndTime() {
2457                    return endTime;
2458            }
2459    
2460            /**
2461             * Set the LocalStormDisplayState property.
2462             * 
2463             * @param value
2464             *            The new value for LocalStormDisplayState
2465             */
2466            public void setLocalStormDisplayState(StormDisplayState value) {
2467                    localStormDisplayState = value;
2468            }
2469    
2470            /**
2471             * Get the LocalStormDisplayState property.
2472             * 
2473             * @return The LocalStormDisplayState
2474             */
2475            public StormDisplayState getLocalStormDisplayState() {
2476                    return localStormDisplayState;
2477            }
2478    
2479            /**
2480             * Set the YearTimeMode property.
2481             * 
2482             * @param value
2483             *            The new value for YearTimeMode
2484             */
2485            public void setYearTimeMode(int value) {
2486                    yearTimeMode = value;
2487            }
2488    
2489            /**
2490             * Get the YearTimeMode property.
2491             * 
2492             * @return The YearTimeMode
2493             */
2494            public int getYearTimeMode() {
2495                    return yearTimeMode;
2496            }
2497    
2498            /**
2499             * Set the EditMode property.
2500             * 
2501             * @param value
2502             *            The new value for EditMode
2503             */
2504            public void setEditMode(boolean value) {
2505                    editMode = value;
2506            }
2507    
2508            /**
2509             * Get the EditMode property.
2510             * 
2511             * @return The EditMode
2512             */
2513            public boolean getEditMode() {
2514                    return editMode;
2515            }
2516    
2517            protected void applyRange() throws VisADException, RemoteException {
2518                    for (StormDisplayState sds : getActiveStorms()) {
2519                            sds.colorRangeChanged();
2520                    }
2521    
2522            }
2523    
2524    }