001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.control.cyclone;
030
031import java.awt.Color;
032import java.awt.Component;
033import java.awt.Container;
034import java.awt.Dimension;
035import java.awt.Point;
036import java.awt.Window;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.awt.event.InputEvent;
040import java.beans.PropertyChangeEvent;
041import java.io.File;
042import java.io.FileOutputStream;
043import java.rmi.RemoteException;
044import java.text.SimpleDateFormat;
045import java.util.ArrayList;
046import java.util.Calendar;
047import java.util.Date;
048import java.util.Enumeration;
049import java.util.GregorianCalendar;
050import java.util.HashMap;
051import java.util.Hashtable;
052import java.util.List;
053import java.util.Vector;
054
055import javax.swing.BorderFactory;
056import javax.swing.ButtonGroup;
057import javax.swing.ImageIcon;
058import javax.swing.JCheckBox;
059import javax.swing.JComboBox;
060import javax.swing.JComponent;
061import javax.swing.JLabel;
062import javax.swing.JMenu;
063import javax.swing.JRadioButton;
064import javax.swing.JScrollPane;
065import javax.swing.JTabbedPane;
066import javax.swing.JWindow;
067import javax.swing.border.BevelBorder;
068
069import org.w3c.dom.Element;
070
071import ucar.unidata.data.BadDataException;
072import ucar.unidata.data.DataChoice;
073import ucar.unidata.data.gis.KmlUtil;
074import ucar.unidata.data.grid.GridUtil;
075import ucar.unidata.data.point.PointOb;
076import ucar.unidata.data.point.PointObFactory;
077import ucar.unidata.data.storm.StormDataSource;
078import ucar.unidata.data.storm.StormInfo;
079import ucar.unidata.data.storm.StormParam;
080import ucar.unidata.data.storm.StormTrack;
081import ucar.unidata.data.storm.StormTrackCollection;
082import ucar.unidata.data.storm.StormTrackPoint;
083import ucar.unidata.data.storm.Way;
084import ucar.unidata.geoloc.LatLonRect;
085import ucar.unidata.idv.MapViewManager;
086import ucar.unidata.idv.control.DisplayControlImpl;
087import ucar.unidata.idv.control.ReadoutInfo;
088import ucar.unidata.ui.TreePanel;
089import ucar.unidata.ui.TwoListPanel;
090import ucar.unidata.ui.symbol.StationModel;
091import ucar.unidata.ui.symbol.StationModelManager;
092import ucar.unidata.util.ColorTable;
093import ucar.unidata.util.DateUtil;
094import ucar.unidata.util.FileManager;
095import ucar.unidata.util.GuiUtils;
096import ucar.unidata.util.IOUtil;
097import ucar.unidata.util.MenuUtil;
098import ucar.unidata.util.Misc;
099import ucar.unidata.util.Range;
100import ucar.unidata.util.StringUtil;
101import ucar.unidata.util.TwoFacedObject;
102import ucar.unidata.view.geoloc.NavigatedDisplay;
103import ucar.unidata.xml.XmlUtil;
104import ucar.visad.Util;
105import ucar.visad.display.CompositeDisplayable;
106import ucar.visad.display.DisplayMaster;
107import ucar.visad.display.StationModelDisplayable;
108import visad.CoordinateSystem;
109import visad.Data;
110import visad.DateTime;
111import visad.DisplayEvent;
112import visad.DoubleSet;
113import visad.FieldImpl;
114import visad.FlatField;
115import visad.FunctionType;
116import visad.GriddedSet;
117import visad.Real;
118import visad.RealType;
119import visad.Set;
120import visad.SetType;
121import visad.TextType;
122import visad.Tuple;
123import visad.Unit;
124import visad.VisADException;
125import visad.georef.EarthLocation;
126import 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
136public 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}