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