001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
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.BorderLayout;
032import java.awt.Color;
033import java.awt.Component;
034import java.awt.Dimension;
035import java.awt.Font;
036import java.awt.Insets;
037import java.awt.event.ActionEvent;
038import java.awt.event.ActionListener;
039import java.awt.event.InputEvent;
040import java.awt.event.KeyEvent;
041import java.io.FileOutputStream;
042import java.rmi.RemoteException;
043import java.util.ArrayList;
044import java.util.Hashtable;
045import java.util.List;
046import java.util.Vector;
047
048import javax.swing.BorderFactory;
049import javax.swing.JButton;
050import javax.swing.JCheckBox;
051import javax.swing.JComboBox;
052import javax.swing.JComponent;
053import javax.swing.JLabel;
054import javax.swing.JList;
055import javax.swing.JPanel;
056import javax.swing.JScrollPane;
057import javax.swing.JTabbedPane;
058import javax.swing.JTable;
059import javax.swing.JTree;
060import javax.swing.ListSelectionModel;
061import javax.swing.event.ListSelectionEvent;
062import javax.swing.event.ListSelectionListener;
063import javax.swing.table.JTableHeader;
064import javax.swing.tree.DefaultTreeCellRenderer;
065
066import org.apache.poi.hssf.usermodel.HSSFCell;
067import org.apache.poi.hssf.usermodel.HSSFRichTextString;
068import org.apache.poi.hssf.usermodel.HSSFRow;
069import org.apache.poi.hssf.usermodel.HSSFSheet;
070import org.apache.poi.hssf.usermodel.HSSFWorkbook;
071import org.w3c.dom.Element;
072
073import ucar.unidata.data.gis.KmlUtil;
074import ucar.unidata.data.storm.StormInfo;
075import ucar.unidata.data.storm.StormParam;
076import ucar.unidata.data.storm.StormTrack;
077import ucar.unidata.data.storm.StormTrackCollection;
078import ucar.unidata.data.storm.StormTrackPoint;
079import ucar.unidata.data.storm.Way;
080import ucar.unidata.geoloc.LatLonPointImpl;
081import ucar.unidata.geoloc.LatLonRect;
082import ucar.unidata.idv.control.ColorTableWidget;
083import ucar.unidata.idv.control.LayoutModelWidget;
084import ucar.unidata.idv.flythrough.FlythroughPoint;
085import ucar.unidata.ui.Command;
086import ucar.unidata.ui.CommandManager;
087import ucar.unidata.ui.TableSorter;
088import ucar.unidata.ui.TreePanel;
089import ucar.unidata.ui.colortable.ColorTableCanvas;
090import ucar.unidata.ui.drawing.Glyph;
091import ucar.unidata.ui.symbol.ShapeSymbol;
092import ucar.unidata.ui.symbol.StationModel;
093import ucar.unidata.ui.symbol.StationModelManager;
094import ucar.unidata.util.ColorTable;
095import ucar.unidata.util.DateUtil;
096import ucar.unidata.util.FileManager;
097import ucar.unidata.util.GuiUtils;
098import ucar.unidata.util.IOUtil;
099import ucar.unidata.util.Misc;
100import ucar.unidata.util.PatternFileFilter;
101import ucar.unidata.util.Range;
102import ucar.visad.display.Animation;
103import ucar.visad.display.CompositeDisplayable;
104import ucar.visad.display.DisplayMaster;
105import ucar.visad.display.Displayable;
106import ucar.visad.display.DisplayableData;
107import ucar.visad.display.LineDrawing;
108import visad.CommonUnit;
109import visad.Data;
110import visad.DateTime;
111import visad.DisplayEvent;
112import visad.Real;
113import visad.RealType;
114import visad.Set;
115import visad.Unit;
116import visad.VisADException;
117import visad.georef.EarthLocation;
118import visad.georef.EarthLocationLite;
119import visad.georef.LatLonPoint;
120
121/**
122 * Part of the McV/IDV implementation of AODT
123 * @author Unidata Development Team, McIDAS-V Development Team
124 * @version $Revision$
125 */
126
127public class StormDisplayState {
128
129        /** _more_ */
130        public static final String PROP_TRACK_TABLE = "prop.track.table";
131
132        /** _more_ */
133        private static String ID_OBS_CONE = "id.obs.cone";
134
135        /** _more_ */
136        private static String ID_OBS_RINGS = "id.obs.rings";
137
138        /** _more_ */
139        private static String ID_OBS_LAYOUTMODEL = "id.obs.layoutmodel";
140
141        /** _more_ */
142        private static String ID_FORECAST_CONE = "id.forecast.cone";
143
144        /** _more_ */
145        private static String ID_FORECAST_RINGS = "id.forecast.rings";
146
147        /** _more_ */
148        private static String ID_FORECAST_COLOR = "id.forecast.color";
149
150        /** _more_ */
151        private static String ID_FORECAST_LAYOUTMODEL = "id.forecast.layoutmodel";
152
153        /** _more_ */
154        private static String ID_OBS_COLOR = "id.obs.color";
155
156        /** The array of colors we cycle through */
157        private static Color[] colors = { Color.RED, Color.PINK, Color.MAGENTA,
158                        Color.ORANGE, Color.YELLOW, Color.GREEN, Color.BLUE, Color.CYAN,
159                        Color.GRAY, Color.LIGHT_GRAY };
160
161        /** _more_ */
162        private boolean hasBeenEdited = false;
163
164        /** _more_ */
165        private boolean colorRangeChanged = false;
166
167        /** _more_ */
168        private static int[] nextColor = { 0 };
169
170        /** _more_ */
171        private JLabel obsColorTableLabel;
172
173        /** _more_ */
174        private JLabel forecastColorTableLabel;
175
176        /** _more_ */
177        private List<StormTrackChart> charts = new ArrayList<StormTrackChart>();
178
179        /** _more_ */
180        private List<StormTrackTableModel> tableModels = new ArrayList<StormTrackTableModel>();
181
182        /** _more_ */
183        private TreePanel tableTreePanel;
184
185        /** _more_ */
186        private Object MUTEX = new Object();
187
188        /** _more_ */
189        private static final Data DUMMY_DATA = new Real(0);
190
191        /** _more_ */
192        private CompositeDisplayable holder;
193
194        /** _more_ */
195        private boolean isOnlyChild = false;
196
197        /** _more_ */
198        private StormInfo stormInfo;
199
200        /** _more_ */
201        private WayDisplayState forecastState;
202
203        /** _more_ */
204        private boolean haveLoadedForecasts = false;
205
206        /** _more_ */
207        private boolean changed = false;
208
209        /** _more_ */
210        private boolean active = false;
211
212        /** _more_ */
213        private StormTrackCollection trackCollection;
214
215        /** _more_ */
216        private StormTrackControl stormTrackControl;
217
218        /** _more_ */
219        private WayDisplayState obsDisplayState;
220
221        /** _more_ */
222        private String obsLayoutModelName = "Storm>Hurricane";
223
224        /** _more_ */
225        private String obsPointLayoutModelName = "Storm>Forecast Hour";
226
227        /** _more_ */
228        private String forecastLayoutModelName = "Storm>Forecast Hour";
229
230        /** time holder */
231        private DisplayableData timesHolder = null;
232
233        /** _more_ */
234        private JComponent mainContents;
235
236        /** _more_ */
237        private JTabbedPane tabbedPane;
238
239        /** _more_ */
240        private JComponent originalContents;
241
242        /** _more_ */
243        private Hashtable params = new Hashtable();
244
245        /** _more_ */
246        private static final int FORECAST_TIME_MODE = 0;
247
248        /** _more_ */
249        private int forecastAnimationMode = FORECAST_TIME_MODE;
250
251        /** _more_ */
252        private JComboBox timeModeBox;
253
254        /** _more_ */
255        private Hashtable<Way, WayDisplayState> wayDisplayStateMap = new Hashtable<Way, WayDisplayState>();
256
257        /** _more_ */
258        private CommandManager commandManager;
259
260        /**
261         * _more_
262         */
263        public StormDisplayState() {
264        }
265
266        /**
267         * _more_
268         * 
269         * @param stormInfo
270         *            _more_
271         * 
272         * @throws Exception
273         *             _more_
274         */
275        public StormDisplayState(StormInfo stormInfo) throws Exception {
276                this.stormInfo = stormInfo;
277                forecastState = new WayDisplayState(this, new Way("forecaststate"));
278                forecastState.getWayState().setVisible(false);
279                forecastState.getConeState().setVisible(true);
280                forecastState.getTrackState().setVisible(true);
281                forecastState.getRingsState().setVisible(true);
282        }
283
284        /**
285         * _more_
286         * 
287         * @return _more_
288         */
289        private CommandManager getCommandManager() {
290                if (commandManager == null) {
291                        commandManager = new CommandManager(100);
292                }
293                return commandManager;
294        }
295
296        /**
297         * _more_
298         */
299        private void checkVisibility() {
300                List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
301                Color bgcolor = Color.lightGray;
302
303                boolean rowOk = forecastState.getWayState().getVisible();
304                forecastState.getRingsState().setBackground(rowOk ? null : bgcolor);
305                forecastState.getConeState().setBackground(rowOk ? null : bgcolor);
306                forecastState.getTrackState().setBackground(rowOk ? null : bgcolor);
307                for (WayDisplayState wds : wayDisplayStates) {
308                        rowOk = wds.getWayState().getVisible();
309                        if (wds.getWay().isObservation()) {
310                                wds.getRingsState().setBackground(rowOk ? null : bgcolor);
311                                wds.getConeState().setBackground(rowOk ? null : bgcolor);
312                                wds.getTrackState().setBackground(rowOk ? null : bgcolor);
313                        } else {
314                                rowOk = rowOk && forecastState.getWayState().getVisible();
315                                wds.getWayState().setBackground(
316                                                forecastState.getWayState().getVisible() ? null
317                                                                : bgcolor);
318                                wds.getRingsState()
319                                                .setBackground(
320                                                                (rowOk && forecastState.getRingsState()
321                                                                                .getVisible()) ? null : bgcolor);
322                                wds.getConeState()
323                                                .setBackground(
324                                                                (rowOk && forecastState.getConeState()
325                                                                                .getVisible()) ? null : bgcolor);
326                                wds.getTrackState()
327                                                .setBackground(
328                                                                (rowOk && forecastState.getTrackState()
329                                                                                .getVisible()) ? null : bgcolor);
330                        }
331                }
332        }
333
334        /**
335         * _more_
336         */
337        public void colorTableChanged() {
338                try {
339                        updateDisplays();
340                } catch (Exception exc) {
341                        stormTrackControl.logException("Changing color table", exc);
342                }
343        }
344
345        /** _more_ */
346        private int wayCnt = -1;
347
348        /** _more_ */
349        private StormTrack editedStormTrack;
350
351        /** _more_ */
352        private StormTrackPoint editedStormTrackPoint;
353
354        /**
355         * _more_
356         * 
357         * @param event
358         *            _more_
359         * 
360         * @throws Exception
361         *             _more_
362         */
363        public void handleEvent(DisplayEvent event) throws Exception {
364                int id = event.getId();
365                InputEvent inputEvent = event.getInputEvent();
366                if ((inputEvent instanceof KeyEvent)) {
367                        KeyEvent keyEvent = (KeyEvent) inputEvent;
368                        if ((keyEvent.getKeyCode() == KeyEvent.VK_Z)
369                                        && keyEvent.isControlDown()) {
370                                getCommandManager().undo();
371                                return;
372                        }
373                        if ((keyEvent.getKeyCode() == KeyEvent.VK_Y)
374                                        && keyEvent.isControlDown()) {
375                                getCommandManager().redo();
376                                return;
377                        }
378                }
379
380                EarthLocation el = stormTrackControl.toEarth(event);
381                LatLonPoint llp = el.getLatLonPoint();
382
383                if (id == DisplayEvent.MOUSE_PRESSED) {
384                        List<StormDisplayState> me = new ArrayList<StormDisplayState>();
385                        me.add(this);
386
387                        // System.err.println ("looking");
388                        Real animationTime = null;
389                        Animation animation = stormTrackControl.getViewAnimation();
390                        if (animation != null) {
391                                animationTime = animation.getAniValue();
392                        }
393                        if (animationTime == null) {
394                                // System.err.println ("no animation");
395                                return;
396                        }
397                        Object[] tuple = stormTrackControl.findClosestPoint(el, me,
398                                        animationTime, 50);
399                        if (tuple == null) {
400                                // System.err.println ("nothing found");
401                                return;
402                        }
403                        editedStormTrack = (StormTrack) tuple[0];
404                        editedStormTrackPoint = (StormTrackPoint) tuple[1];
405                }
406
407                if (id == DisplayEvent.MOUSE_DRAGGED) {
408                        if (editedStormTrackPoint == null) {
409                                return;
410                        }
411                        handleMouseDrag(event, el);
412                }
413
414                if (id == DisplayEvent.MOUSE_RELEASED) {
415                        editedStormTrackPoint = null;
416                        editedStormTrack = null;
417                }
418        }
419
420        /**
421         * Class PointEditCommand _more_
422         * 
423         * 
424         * @author IDV Development Team
425         */
426        private class PointEditCommand extends Command {
427
428                /** _more_ */
429                StormTrack stormTrack;
430
431                /** _more_ */
432                List<StormTrackPoint> originalPoints;
433
434                /** _more_ */
435                List<StormTrackPoint> newPoints;
436
437                /**
438                 * _more_
439                 * 
440                 * @param stormTrack
441                 *            _more_
442                 * @param originalPoints
443                 *            _more_
444                 * @param newPoints
445                 *            _more_
446                 */
447                public PointEditCommand(StormTrack stormTrack,
448                                List<StormTrackPoint> originalPoints,
449                                List<StormTrackPoint> newPoints) {
450                        this.stormTrack = stormTrack;
451                        this.originalPoints = originalPoints;
452                        this.newPoints = newPoints;
453                }
454
455                /**
456                 * _more_
457                 */
458                public void redoCommand() {
459                        try {
460                                stormTrack.setTrackPoints(newPoints);
461                                updateDisplays(stormTrack);
462                        } catch (Exception exp) {
463                                stormTrackControl.logException("undoing edit command", exp);
464                        }
465                }
466
467                /**
468                 * Undo
469                 */
470                public void undoCommand() {
471                        try {
472                                stormTrack.setTrackPoints(originalPoints);
473                                updateDisplays(stormTrack);
474                        } catch (Exception exp) {
475                                stormTrackControl.logException("undoing edit command", exp);
476                        }
477                }
478
479        }
480
481        /**
482         * _more_
483         * 
484         * @param event
485         *            _more_
486         * @param newPt
487         *            _more_
488         * 
489         * @throws Exception
490         *             _more_
491         */
492        private void handleMouseDrag(DisplayEvent event, EarthLocation newPt)
493                        throws Exception {
494                List<StormTrackPoint> points = editedStormTrack.getTrackPoints();
495                List<StormTrackPoint> originalPoints = new ArrayList<StormTrackPoint>();
496                for (StormTrackPoint stp : points) {
497                        originalPoints.add(new StormTrackPoint(stp));
498                }
499
500                // if the control key is not down then just move the point
501                int stretchIndex = editedStormTrack.indexOf(editedStormTrackPoint);
502                if (stretchIndex < 0) {
503                        // this should never happen
504                        throw new IllegalStateException("Cannot find track point");
505                }
506
507                EarthLocation oldPt = (EarthLocation) points.get(stretchIndex)
508                                .getLocation();
509
510                double deltaY = oldPt.getLatitude().getValue(CommonUnit.degree)
511                                - newPt.getLatitude().getValue(CommonUnit.degree);
512                double deltaX = LatLonPointImpl.lonNormal(oldPt.getLongitude()
513                                .getValue(CommonUnit.degree))
514                                - LatLonPointImpl.lonNormal(newPt.getLongitude().getValue(
515                                                CommonUnit.degree));
516
517                if ((event.getModifiers() & event.CTRL_MASK) != 0) {
518                        editedStormTrackPoint.setLocation(newPt);
519                        // else do an interpolated stretch
520                        int startPts = stretchIndex - 1;
521                        int endPts = points.size() - stretchIndex;
522                        double percent = 1.0;
523
524                        // System.err.println("delta: " + deltaX + " " + deltaY);
525                        for (int i = stretchIndex - 1; i >= 0; i--) {
526                                percent -= 1.0 / (double) startPts;
527                                if (percent <= 0.05) {
528                                        break;
529                                }
530                                EarthLocation pt = (EarthLocation) points.get(i).getLocation();
531                                EarthLocation newEl = makePoint(pt.getLatitude().getValue(
532                                                CommonUnit.degree)
533                                                - deltaY * percent, LatLonPointImpl.lonNormal(pt
534                                                .getLongitude().getValue(CommonUnit.degree))
535                                                - deltaX * percent);
536                                // System.err.println("   " +percent + " " + pt.getLatLonPoint()
537                                // + " " + newEl.getLatLonPoint());
538                                points.get(i).setLocation(newEl);
539                        }
540                        percent = 1.0;
541                        for (int i = stretchIndex + 1; i < points.size(); i++) {
542                                percent -= 1.0 / (double) endPts;
543                                if (percent <= 0.05) {
544                                        break;
545                                }
546                                EarthLocation pt = (EarthLocation) points.get(i).getLocation();
547                                EarthLocation newEl = makePoint(pt.getLatitude().getValue(
548                                                CommonUnit.degree)
549                                                - deltaY * percent, LatLonPointImpl.lonNormal(pt
550                                                .getLongitude().getValue(CommonUnit.degree))
551                                                - deltaX * percent);
552                                points.get(i).setLocation(newEl);
553                        }
554                } else if ((event.getModifiers() & event.SHIFT_MASK) != 0) {
555                        for (StormTrackPoint stp : points) {
556                                EarthLocation pt = (EarthLocation) stp.getLocation();
557                                EarthLocation newEl = makePoint(pt.getLatitude().getValue(
558                                                CommonUnit.degree)
559                                                - deltaY, LatLonPointImpl.lonNormal(pt.getLongitude()
560                                                .getValue(CommonUnit.degree))
561                                                - deltaX);
562                                stp.setLocation(newEl);
563                        }
564                } else {
565                        editedStormTrackPoint.setLocation(newPt);
566                }
567
568                getCommandManager().add(
569                                new PointEditCommand(editedStormTrack, originalPoints,
570                                                editedStormTrack.getTrackPoints()));
571                updateDisplays(editedStormTrack);
572        }
573
574        /**
575         * _more_
576         * 
577         * @param latitude
578         *            _more_
579         * @param longitude
580         *            _more_
581         * 
582         * @return _more_
583         * 
584         * @throws RemoteException
585         *             _more_
586         * @throws VisADException
587         *             _more_
588         */
589        protected EarthLocation makePoint(double latitude, double longitude)
590                        throws VisADException, RemoteException {
591                Real altReal = new Real(RealType.Altitude, 0);
592                return new EarthLocationLite(new Real(RealType.Latitude, latitude),
593                                new Real(RealType.Longitude, longitude), altReal);
594        }
595
596        /**
597         * Check if its ok to show the given way. if we have less than 2 ways total
598         * then always showit
599         * 
600         * @param way
601         *            _more_
602         * 
603         * @return _more_
604         */
605        protected boolean okToShowWay(Way way) {
606                if (wayCnt == -1) {
607
608                        List<StormTrack> tracks = trackCollection.getTracks();
609                        Hashtable ways = new Hashtable();
610                        wayCnt = 0;
611                        for (StormTrack track : tracks) {
612                                if (ways.get(track.getWay()) == null) {
613                                        wayCnt++;
614                                        ways.put(track.getWay(), "");
615                                }
616                        }
617                }
618                if (wayCnt <= 1) {
619                        return true;
620                }
621                return stormTrackControl.okToShowWay(way);
622        }
623
624        /**
625         * _more_
626         * 
627         * @return _more_
628         */
629        public LatLonRect getBoundingBox() {
630                if (trackCollection == null) {
631                        return null;
632                }
633                double minLon = Double.POSITIVE_INFINITY;
634                double maxLon = Double.NEGATIVE_INFINITY;
635                double minLat = Double.POSITIVE_INFINITY;
636                double maxLat = Double.NEGATIVE_INFINITY;
637
638                boolean didone = false;
639                List<StormTrack> tracks = trackCollection.getTracks();
640                for (StormTrack track : tracks) {
641                        if (!okToShowWay(track.getWay())) {
642                                continue;
643                        }
644                        LatLonRect bbox = track.getBoundingBox();
645                        if (bbox == null) {
646                                continue;
647                        }
648                        minLon = Math.min(minLon, bbox.getLonMin());
649                        maxLon = Math.max(maxLon, bbox.getLonMax());
650                        minLat = Math.min(minLat, bbox.getLatMin());
651                        maxLat = Math.max(maxLat, bbox.getLatMax());
652                        didone = true;
653                }
654                if (!didone) {
655                        return null;
656                }
657                return new LatLonRect(new LatLonPointImpl(maxLat, minLon),
658                                new LatLonPointImpl(minLat, maxLon));
659        }
660
661        /**
662         * _more_
663         * 
664         * @param isOnlyChild
665         *            _more_
666         */
667        protected void setIsOnlyChild(boolean isOnlyChild) {
668                this.isOnlyChild = isOnlyChild;
669        }
670
671        /**
672         * _more_
673         * 
674         * @return _more_
675         */
676        public JComponent getContents() {
677                if (mainContents == null) {
678                        mainContents = doMakeContents();
679                }
680                return mainContents;
681        }
682
683        /**
684         * _more_
685         * 
686         * @return _more_
687         */
688        protected List<WayDisplayState> getWayDisplayStates() {
689                return (List<WayDisplayState>) Misc.toList(wayDisplayStateMap
690                                .elements());
691        }
692
693        /**
694         * _more_
695         * 
696         * @param way
697         *            _more_
698         * 
699         * @return _more_
700         */
701        protected WayDisplayState getWayDisplayState(Way way) {
702                WayDisplayState wayState = wayDisplayStateMap.get(way);
703                if (wayState == null) {
704                        wayDisplayStateMap.put(way, wayState = new WayDisplayState(this,
705                                        way));
706                        // "idv.stormtrackcontrol.way.color"
707                        if (wayState.getColor() == null) {
708                                wayState.setColor(getNextColor(nextColor));
709                        }
710                }
711                return wayState;
712        }
713
714        /**
715         * _more_
716         */
717        protected void reload() {
718                if (!active) {
719                        return;
720                }
721                deactivate();
722                loadStorm();
723        }
724
725        /**
726         * _more_
727         */
728        public void loadStorm() {
729                if (active) {
730                        return;
731                }
732                active = true;
733                showStorm();
734        }
735
736        /**
737         * _more_
738         */
739        protected void reloadChart() {
740                for (StormTrackChart stormTrackChart : charts) {
741                        // stormTrackChart.deactivate();
742                        stormTrackChart.updateChart();
743                }
744        }
745
746        /**
747         * _more_
748         * 
749         * @return _more_
750         */
751        protected StormTrackCollection getTrackCollection() {
752                return trackCollection;
753        }
754
755        /**
756         * _more_
757         * 
758         * @param sm
759         *            _more_
760         */
761        public void setObsLayoutModel(StationModel sm) {
762                obsLayoutModelName = ((sm == null) ? null : sm.getName());
763                updateLayoutModel(true);
764        }
765
766        /**
767         * _more_
768         * 
769         * @param sm
770         *            _more_
771         */
772        public void setObsPointLayoutModel(StationModel sm) {
773                obsPointLayoutModelName = ((sm == null) ? null : sm.getName());
774                updateLayoutModel(true);
775        }
776
777        /**
778         * _more_
779         * 
780         * @param sm
781         *            _more_
782         */
783        public void setForecastLayoutModel(StationModel sm) {
784                forecastLayoutModelName = ((sm == null) ? null : sm.getName());
785                updateLayoutModel(false);
786        }
787
788        /**
789         * _more_
790         * 
791         * @param name
792         *            _more_
793         */
794        protected void handleChangedStationModel(String name) {
795                if (Misc.equals(obsLayoutModelName, name)) {
796                        updateLayoutModel(true);
797                }
798                if (Misc.equals(forecastLayoutModelName, name)) {
799                        updateLayoutModel(false);
800                }
801        }
802
803        /**
804         * _more_
805         * 
806         * @param forObs
807         *            _more_
808         */
809        public void updateLayoutModel(boolean forObs) {
810                List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
811                try {
812                        for (WayDisplayState wds : wayDisplayStates) {
813                                if (wds.getWay().isObservation() && !forObs) {
814                                        continue;
815                                }
816                                wds.updateLayoutModel();
817                        }
818                } catch (Exception exc) {
819                        stormTrackControl.logException("Updating layout models", exc);
820                }
821        }
822
823        /**
824         * _more_
825         */
826        public void deactivate() {
827                try {
828                        for (StormTrackChart stormTrackChart : charts) {
829                                stormTrackChart.deactivate();
830                        }
831                        trackCollection = null;
832                        active = false;
833                        colorRangeChanged = false;
834                        stormTrackControl.removeDisplayable(holder);
835                        holder = null;
836                        if (mainContents != null) {
837                                mainContents.removeAll();
838                                mainContents.add(BorderLayout.NORTH, originalContents);
839                                List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
840                                for (WayDisplayState wayDisplayState : wayDisplayStates) {
841                                        wayDisplayState.deactivate();
842                                }
843                                mainContents.repaint(1);
844                        }
845                        stormTrackControl.stormChanged(StormDisplayState.this);
846
847                } catch (Exception exc) {
848                        stormTrackControl.logException("Deactivating storm", exc);
849                }
850        }
851
852        /**
853         * _more_
854         * 
855         * @return _more_
856         */
857        private JComponent doMakeContents() {
858                JButton loadBtn = new JButton("Load Tracks:");
859                JLabel topLabel = GuiUtils.cLabel("  " + stormInfo);
860                loadBtn.addActionListener(new ActionListener() {
861                        public void actionPerformed(ActionEvent ae) {
862                                loadStorm();
863                        }
864                });
865
866                JComponent top = GuiUtils.hbox(loadBtn, topLabel);
867                originalContents = GuiUtils.inset(top, 5);
868                JComponent contents = GuiUtils.top(originalContents);
869                final int cnt = xcnt++;
870                contents = new JPanel(new BorderLayout());
871                contents.add(BorderLayout.NORTH, originalContents);
872                return contents;
873        }
874
875        /** _more_ */
876        static int xcnt = 0;
877
878        /**
879         * _more_
880         */
881        public void initDone() {
882                if (getActive()) {
883                        showStorm();
884                }
885
886        }
887
888        /**
889         * _more_
890         * 
891         * @return _more_
892         */
893        public boolean getForecastVisible() {
894                // return forecastState.getVisible();
895                return forecastState.getWayState().getVisible();
896        }
897
898        /**
899         * _more_
900         * 
901         * @param stormParams
902         *            _more_
903         * @param id
904         *            _more_
905         * 
906         * @return _more_
907         */
908        private JComponent makeList(List stormParams, final Object id) {
909                if ((stormParams == null) || (stormParams.size() == 0)) {
910                        return GuiUtils.filler(2, 10);
911                }
912                final JList list = new JList(new Vector(stormParams));
913                list.setVisibleRowCount(3);
914                list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
915                list.addListSelectionListener(new ListSelectionListener() {
916                        public void valueChanged(ListSelectionEvent e) {
917                                List<StormParam> selected = new ArrayList<StormParam>();
918                                selected.addAll(Misc.toList(list.getSelectedValues()));
919                                try {
920                                        params.put(id, selected);
921                                        updateDisplays();
922                                } catch (Exception exc) {
923                                        stormTrackControl.logException("setting cones", exc);
924                                }
925
926                        }
927                });
928
929                list
930                                .setToolTipText("<html>Parameter used for cone<br>Control-click for multiple select</html>");
931                List selected = (List) params.get(id);
932                if ((selected != null) && (selected.size() > 0)) {
933                        int[] indices = new int[selected.size()];
934                        for (int i = 0; i < selected.size(); i++) {
935                                indices[i] = stormParams.indexOf(selected.get(i));
936                        }
937                        list.setSelectedIndices(indices);
938                }
939
940                JScrollPane sp = new JScrollPane(list);
941                return sp;
942        }
943
944        /**
945         * _more_
946         * 
947         * @param stormParams
948         *            _more_
949         * @param id
950         *            _more_
951         * @param tooltip
952         *            _more_
953         * 
954         * @return _more_
955         */
956        private JComponent makeBox(List stormParams, final Object id, String tooltip) {
957                if ((stormParams == null) || (stormParams.size() == 0)) {
958                        return GuiUtils.filler(2, 10);
959                }
960                final JComboBox box = new JComboBox(new Vector(stormParams));
961                box.setToolTipText(tooltip);
962                StormParam stormParam = (StormParam) params.get(id);
963                if (stormParam != null) {
964                        box.setSelectedItem(stormParam);
965                }
966                box.addActionListener(new ActionListener() {
967                        public void actionPerformed(ActionEvent ae) {
968                                Object selected = box.getSelectedItem();
969                                if ((selected == null) || (selected instanceof String)) {
970                                        params.remove(id);
971                                } else {
972                                        params.put(id, selected);
973                                }
974                                try {
975                                        colorRangeChanged = false;
976                                        updateDisplays();
977                                } catch (Exception exc) {
978                                        stormTrackControl.logException("setting cones", exc);
979                                }
980
981                        }
982                });
983
984                return box;
985        }
986
987        /**
988         * _more_
989         * 
990         * @param params
991         *            _more_
992         * 
993         * @return _more_
994         */
995        private List<StormParam> getDistanceParams(List<StormParam> params) {
996                if ((params == null) || (params.size() == 0)) {
997                        return null;
998                }
999
1000                List<StormParam> attrNames = new ArrayList<StormParam>();
1001                for (StormParam param : params) {
1002                        if (Unit.canConvert(param.getUnit(), CommonUnit.meter)) {
1003
1004                                attrNames.add(param);
1005                        }
1006
1007                }
1008                if (attrNames.size() == 0) {
1009                        return null;
1010                }
1011                return attrNames;
1012        }
1013
1014        // RealType fixedtype;
1015        StormParam getFixedParam() {
1016                RealType rtype = RealType.getRealType("Fixed");
1017//              if (rtype == null) {
1018//                      try {
1019//                              rtype = new RealType("Fixed");
1020//                      } catch (VisADException e) {
1021//
1022//                      }
1023//                      // fixedtype=rtype;
1024//              }
1025                return new StormParam(rtype, false, false);
1026        }
1027
1028        /**
1029         * _more_
1030         */
1031        private void initCenterContents() {
1032
1033                if (mainContents == null) {
1034                        return;
1035                }
1036
1037                mainContents.removeAll();
1038                JButton unloadBtn = GuiUtils.makeImageButton(
1039                                "/auxdata/ui/icons/Cut16.gif", this, "deactivate");
1040                unloadBtn.setToolTipText("Remove this storm");
1041                String label = "Storm: "
1042                                + stormInfo.toString()
1043                                + "   "
1044                                + stormInfo.getStartTime().formattedString("yyyy-MM-dd",
1045                                                DateUtil.TIMEZONE_GMT);
1046
1047                JComponent top = GuiUtils.inset(GuiUtils.leftRight(GuiUtils
1048                                .lLabel(label), unloadBtn), new Insets(0, 0, 0, 0));
1049
1050                List<StormParam> forecastParams = new ArrayList<StormParam>();
1051                Hashtable seenParams = new Hashtable();
1052                List<StormParam> obsParams = new ArrayList<StormParam>();
1053                Hashtable seenWays = new Hashtable();
1054                for (StormTrack track : trackCollection.getTracks()) {
1055                        // if (seenWays.get(track.getWay()) != null) {
1056                        // continue;
1057                        // }
1058                        // seenWays.put(track.getWay(), track.getWay());
1059                        List<StormParam> trackParams = track.getParams();
1060                        if (track.getWay().isObservation()) {
1061                                obsParams.addAll(trackParams);
1062                                continue;
1063                        }
1064                        for (StormParam param : trackParams) {
1065                                if (seenParams.get(param) != null) {
1066                                        continue;
1067                                }
1068                                seenParams.put(param, param);
1069                                forecastParams.add(param);
1070                        }
1071                }
1072
1073                List<StormParam> forecastRadiusParams = getDistanceParams(forecastParams);
1074                List<StormParam> obsRadiusParams = getDistanceParams(obsParams);
1075
1076                if (obsRadiusParams != null) {
1077                        // If its not set then set it
1078                        if (params.get(ID_OBS_RINGS) == null) {
1079                                params.put(ID_OBS_RINGS, obsRadiusParams.get(0));
1080                        }
1081                        if (params.get(ID_OBS_CONE) == null) {
1082                                params.put(ID_OBS_CONE, Misc.newList(obsRadiusParams.get(0)));
1083                        }
1084                }
1085                if (forecastRadiusParams != null) {
1086                        // If its not set then set it
1087                        if (params.get(ID_FORECAST_RINGS) == null) {
1088                                params.put(ID_FORECAST_RINGS, forecastRadiusParams.get(0));
1089                        }
1090                        if (params.get(ID_FORECAST_CONE) == null) {
1091                                params.put(ID_FORECAST_CONE, Misc.newList(forecastRadiusParams
1092                                                .get(0)));
1093                        }
1094                }
1095
1096                // Sort them by name
1097
1098                List<Way> ways = Misc.sort(trackCollection.getWayList());
1099                boolean haveDoneForecast = false;
1100                List<String> colLabels = (List<String>) Misc.newList("", "Show",
1101                                "Track");
1102                if ((forecastRadiusParams != null) || (obsRadiusParams != null)) {
1103                        colLabels.add("Rings");
1104                        colLabels.add("Cone");
1105                }
1106                int numCols = colLabels.size();
1107
1108                List obsColorParams = new ArrayList(obsParams);
1109                List forecastColorParams = new ArrayList(forecastParams);
1110                obsColorParams.add(0, getFixedParam());
1111                forecastColorParams.add(0, getFixedParam());
1112
1113                JComponent obsLayoutComp = new LayoutModelWidget(stormTrackControl,
1114                                this, "setObsLayoutModel", getObsLayoutModel(), true);
1115                JComponent obsPointLayoutComp = new LayoutModelWidget(
1116                                stormTrackControl, this, "setObsPointLayoutModel",
1117                                getObsPointLayoutModel(), true);
1118                JComponent forecastLayoutComp = new LayoutModelWidget(
1119                                stormTrackControl, this, "setForecastLayoutModel",
1120                                getForecastLayoutModel(), true);
1121
1122                JComponent obsColorByBox = makeBox(obsColorParams, ID_OBS_COLOR,
1123                                "Parameter used for coloring observation track");
1124                JComponent forecastColorByBox = makeBox(forecastColorParams,
1125                                ID_FORECAST_COLOR,
1126                                "Parameter used for coloring forecast tracks");
1127
1128                JComponent obsConeComp = ((obsRadiusParams != null) ? makeList(
1129                                obsRadiusParams, ID_OBS_CONE) : (JComponent) GuiUtils.filler());
1130                JComponent obsRingComp = ((obsRadiusParams != null) ? makeBox(
1131                                obsRadiusParams, ID_OBS_RINGS,
1132                                "Parameter used for observation rings") : (JComponent) GuiUtils
1133                                .filler());
1134
1135                JComponent forecastConeComp = ((forecastRadiusParams != null) ? makeList(
1136                                forecastRadiusParams, ID_FORECAST_CONE)
1137                                : (JComponent) GuiUtils.filler());
1138                JComponent forecastRingComp = ((forecastRadiusParams != null) ? makeBox(
1139                                forecastRadiusParams, ID_FORECAST_RINGS,
1140                                "Parameter used for forecast rings")
1141                                : (JComponent) GuiUtils.filler());
1142
1143                List topComps = new ArrayList();
1144
1145                timeModeBox = new JComboBox(new Vector(Misc.newList("On", "Off")));
1146                timeModeBox.setSelectedIndex(forecastAnimationMode);
1147                timeModeBox.addActionListener(new ActionListener() {
1148                        public void actionPerformed(ActionEvent ae) {
1149                                forecastAnimationMode = timeModeBox.getSelectedIndex();
1150                                try {
1151                                        // reload();
1152                                        updateDisplays();
1153                                } catch (Exception exc) {
1154                                        stormTrackControl.logException(
1155                                                        "change forecast animation mode", exc);
1156                                }
1157                        }
1158                });
1159                timeModeBox.setToolTipText("Animate tracks or show all tracks.");
1160
1161                JComponent forecastModeComp = GuiUtils.inset(GuiUtils.left(GuiUtils
1162                                .label("Animation Mode: ", timeModeBox)), 5);
1163
1164                topComps.add(new JLabel(""));
1165                topComps.add(GuiUtils.cLabel("<html><u><i>Observation</i></u></html>"));
1166                topComps.add(GuiUtils.cLabel("<html><u><i>Forecast</i></u></html>"));
1167
1168                topComps.add(GuiUtils.rLabel("Points:"));
1169                topComps.add(obsPointLayoutComp);
1170                topComps.add(forecastLayoutComp);
1171
1172                topComps.add(GuiUtils.rLabel("Animation:"));
1173                topComps.add(obsLayoutComp);
1174                topComps.add(forecastModeComp); // GuiUtils.filler());
1175
1176                forecastColorTableLabel = new JLabel(" ");
1177                forecastColorTableLabel.setToolTipText("Color table preview");
1178                obsColorTableLabel = new JLabel(" ");
1179                obsColorTableLabel.setToolTipText("Color table preview");
1180
1181                topComps.add(GuiUtils.rLabel("Color By:"));
1182                topComps.add(GuiUtils.vbox(obsColorByBox, obsColorTableLabel));
1183                topComps
1184                                .add(GuiUtils.vbox(forecastColorByBox, forecastColorTableLabel));
1185
1186                if ((forecastRadiusParams != null) || (obsRadiusParams != null)) {
1187                        topComps.add(GuiUtils.rLabel("Rings:"));
1188                        topComps.add(obsRingComp);
1189                        topComps.add(forecastRingComp);
1190                        topComps.add(GuiUtils.rLabel("Cone:"));
1191                        topComps.add(obsConeComp);
1192                        topComps.add(forecastConeComp);
1193                }
1194
1195                GuiUtils.tmpInsets = new Insets(4, 4, 2, 2);
1196                JComponent paramComp = GuiUtils.doLayout(topComps, 3, GuiUtils.WT_N,
1197                                GuiUtils.WT_N);
1198
1199                List comps = new ArrayList();
1200
1201                for (Way way : ways) {
1202                        WayDisplayState wds = getWayDisplayState(way);
1203                        if (!okToShowWay(wds.getWay())) {
1204                                continue;
1205                        }
1206                        JComponent labelComp = GuiUtils.hbox(wds.getWayState()
1207                                        .getCheckBox(), new JLabel(" " + way.toString()));
1208
1209                        JComponent swatch = GuiUtils.wrap(wds.getColorSwatch());
1210                        if (way.isObservation()) {
1211                                // We put the obs in the front of the list
1212                                int col = 0;
1213                                comps.add(col++, swatch);
1214                                comps.add(col++, labelComp);
1215                                comps.add(col++, GuiUtils.wrap(wds.getTrackState()
1216                                                .getCheckBox()));
1217                                if (obsRadiusParams != null) {
1218                                        comps.add(col++, GuiUtils.wrap(wds.getRingsState()
1219                                                        .getCheckBox()));
1220                                        comps.add(col++, GuiUtils.wrap(wds.getConeState()
1221                                                        .getCheckBox()));
1222                                }
1223
1224                        } else {
1225                                if (!haveDoneForecast) {
1226
1227                                        // Put the forecast info here
1228                                        haveDoneForecast = true;
1229                                        for (int colIdx = 0; colIdx < numCols; colIdx++) {
1230                                                comps.add(GuiUtils.filler());
1231                                        }
1232
1233                                        comps.add(GuiUtils.filler());
1234                                        comps.add(GuiUtils.hbox(forecastState.getWayState()
1235                                                        .getCheckBox(), GuiUtils
1236                                                        .lLabel("<html><u><i>Forecasts:</i></u></html>")));
1237                                        comps.add(GuiUtils.wrap(forecastState.getTrackState()
1238                                                        .getCheckBox()));
1239                                        if (forecastRadiusParams != null) {
1240                                                comps.add(GuiUtils.wrap(forecastState.getRingsState()
1241                                                                .getCheckBox()));
1242
1243                                                comps.add(GuiUtils.wrap(forecastState.getConeState()
1244                                                                .getCheckBox()));
1245                                        }
1246                                }
1247                                comps.add(swatch);
1248                                comps.add(labelComp);
1249                                comps.add(GuiUtils.wrap(wds.getTrackState().getCheckBox()));
1250                                if (forecastRadiusParams != null) {
1251                                        comps.add(GuiUtils.wrap(wds.getRingsState().getCheckBox()));
1252                                        comps.add(GuiUtils.wrap(wds.getConeState().getCheckBox()));
1253                                }
1254                        }
1255                }
1256
1257                for (int colIdx = 0; colIdx < numCols; colIdx++) {
1258                        String s = colLabels.get(colIdx);
1259                        if (s.length() > 0) {
1260                                comps.add(colIdx, new JLabel("<html><u><i>" + s
1261                                                + "</i></u></html>"));
1262                        } else {
1263                                comps.add(colIdx, new JLabel(""));
1264                        }
1265                }
1266
1267                GuiUtils.tmpInsets = new Insets(2, 2, 0, 2);
1268                JComponent wayComp = GuiUtils.topLeft(GuiUtils.doLayout(comps, numCols,
1269                                GuiUtils.WT_N, GuiUtils.WT_N));
1270                // Put the list of ways into a scroller if there are lots of them
1271                if (ways.size() > 6) {
1272                        int width = 300;
1273                        int height = 200;
1274                        JScrollPane scroller = GuiUtils.makeScrollPane(wayComp, width,
1275                                        height);
1276                        scroller.setBorder(BorderFactory.createLoweredBevelBorder());
1277                        scroller.setPreferredSize(new Dimension(width, height));
1278                        scroller.setMinimumSize(new Dimension(width, height));
1279                        wayComp = scroller;
1280                }
1281
1282                wayComp = GuiUtils.left(GuiUtils.doLayout(new Component[] {
1283                                GuiUtils.left(paramComp), GuiUtils.filler(2, 10),
1284                                GuiUtils.left(wayComp) }, 1, GuiUtils.WT_N, GuiUtils.WT_NNY));
1285
1286                wayComp = GuiUtils.inset(wayComp, new Insets(0, 5, 0, 0));
1287                // tabbedPane = GuiUtils.getNestedTabbedPane();
1288                tabbedPane = new JTabbedPane();
1289                tabbedPane.addTab("Tracks", wayComp);
1290                tabbedPane.addTab("Table", getTrackTable());
1291
1292                if (charts.size() == 0) {
1293                        charts.add(new StormTrackChart(this, "Storm Chart",
1294                                        StormTrackChart.MODE_FORECASTTIME));
1295                }
1296                for (StormTrackChart stormTrackChart : charts) {
1297                        tabbedPane.addTab(stormTrackChart.getName(), stormTrackChart
1298                                        .getContents());
1299                }
1300
1301                JComponent inner = GuiUtils.topCenter(top, tabbedPane);
1302                inner = GuiUtils.inset(inner, 5);
1303                mainContents.add(BorderLayout.CENTER, inner);
1304                mainContents.invalidate();
1305                mainContents.validate();
1306                mainContents.repaint();
1307
1308                checkVisibility();
1309        }
1310
1311        /**
1312         * Class ParamSelector _more_
1313         * 
1314         * 
1315         * @author IDV Development Team
1316         * @version $Revision$
1317         */
1318        private static class ParamSelector {
1319
1320                /** _more_ */
1321                List<StormParam> params;
1322
1323                /** _more_ */
1324                JList list;
1325
1326                /**
1327                 * _more_
1328                 * 
1329                 * @param types
1330                 *            _more_
1331                 */
1332                public ParamSelector(List<StormParam> types) {
1333                }
1334        }
1335
1336        /**
1337         * _more_
1338         * 
1339         * @param time
1340         *            _more_
1341         */
1342        protected void timeChanged(Real time) {
1343                for (StormTrackChart stormTrackChart : charts) {
1344                        stormTrackChart.timeChanged(time);
1345                }
1346        }
1347
1348        /**
1349         * _more_
1350         * 
1351         * @param way
1352         *            _more_
1353         * 
1354         * @return _more_
1355         */
1356        protected boolean canShowWay(Way way) {
1357                return getWayDisplayState(way).getWayState().getVisible();
1358        }
1359
1360        /**
1361         * _more_
1362         * 
1363         * @param stormTrackControl
1364         *            _more_
1365         */
1366        protected void setStormTrackControl(StormTrackControl stormTrackControl) {
1367                this.stormTrackControl = stormTrackControl;
1368        }
1369
1370        /**
1371         * _more_
1372         */
1373        protected void showStorm() {
1374                Misc.run(new Runnable() {
1375                        public void run() {
1376                                DisplayMaster displayMaster = stormTrackControl
1377                                                .getDisplayMaster();
1378                                boolean wasActive = displayMaster.isActive();
1379                                displayMaster.setDisplayInactive();
1380                                try {
1381                                        synchronized (MUTEX) {
1382                                                stormTrackControl.showWaitCursor();
1383                                                showStormInner();
1384                                                stormTrackControl.stormChanged(StormDisplayState.this);
1385                                        }
1386                                } catch (Exception exc) {
1387                                        stormTrackControl.logException("Showing storm", exc);
1388                                } finally {
1389                                        stormTrackControl.showNormalCursor();
1390                                        if (wasActive) {
1391                                                try {
1392//                                                      displayMaster.setActive(true);
1393                                                    displayMaster.setDisplayActive();
1394                                                } catch (Exception exc) {
1395                                                }
1396                                        }
1397                                }
1398
1399                        }
1400                });
1401        }
1402
1403        /**
1404         * _more_
1405         * 
1406         * @param displayable
1407         *            _more_
1408         * 
1409         * @throws RemoteException
1410         *             _more_
1411         * @throws VisADException
1412         *             _more_
1413         */
1414        protected void addDisplayable(Displayable displayable)
1415                        throws VisADException, RemoteException {
1416                if (holder != null) {
1417                        holder.addDisplayable(displayable);
1418                }
1419        }
1420
1421        /**
1422         * _more_
1423         * 
1424         * @return _more_
1425         */
1426        protected StormTrackControl getStormTrackControl() {
1427                return stormTrackControl;
1428        }
1429
1430        /**
1431         * _more_
1432         * 
1433         * 
1434         * @throws Exception
1435         *             _more_
1436         */
1437        private void showStormInner() throws Exception {
1438
1439                // Read the tracks if we haven't
1440                long t1 = System.currentTimeMillis();
1441                if (trackCollection == null) {
1442                        if (mainContents != null) {
1443                                mainContents.removeAll();
1444                                mainContents.add(GuiUtils.top(GuiUtils.inset(new JLabel(
1445                                                "Loading Tracks..."), 5)));
1446                                mainContents.invalidate();
1447                                mainContents.validate();
1448                                mainContents.repaint();
1449                        }
1450
1451                        trackCollection = stormTrackControl.getStormDataSource()
1452                                        .getTrackCollection(stormInfo,
1453                                                        stormTrackControl.getOkWays(),
1454                                                        stormTrackControl.getObservationWay());
1455                        initCenterContents();
1456                        stormTrackControl
1457                                        .addDisplayable(holder = new CompositeDisplayable());
1458                        // Add the tracks
1459                        for (StormTrack track : trackCollection.getTracks()) {
1460                                WayDisplayState wayDisplayState = getWayDisplayState(track
1461                                                .getWay());
1462                                wayDisplayState.addTrack(track);
1463                        }
1464                        obsDisplayState = getWayDisplayState(Way.OBSERVATION);
1465                        StormTrack obsTrack = trackCollection.getObsTrack();
1466
1467                        List<DateTime> times = new ArrayList<DateTime>();
1468                        if (obsTrack != null) {
1469                                times = obsTrack.getTrackTimes();
1470                        } else {
1471                                for (StormTrack track : trackCollection.getTracks()) {
1472                                        times.add(track.getStartTime());
1473                                }
1474                        }
1475                        if (times.size() > 0) {
1476                                times = (List<DateTime>) Misc.sort(Misc.makeUnique(times));
1477                                timesHolder = new LineDrawing("track_time"
1478                                                + stormInfo.getStormId());
1479                                timesHolder.setManipulable(false);
1480                                timesHolder.setVisible(false);
1481                                Set timeSet = ucar.visad.Util.makeTimeSet(times);
1482                                // System.err.println("time set:" + timeSet);
1483                                timesHolder.setData(timeSet);
1484                                holder.addDisplayable(timesHolder);
1485                        }
1486                }
1487
1488                updateDisplays();
1489                updateCharts();
1490
1491                long t2 = System.currentTimeMillis();
1492                // System.err.println("time:" + (t2 - t1));
1493        }
1494
1495        /**
1496         * _more_
1497         * 
1498         * @param id
1499         *            _more_
1500         * 
1501         * @return _more_
1502         */
1503        protected List<StormParam> getParams(Object id) {
1504                List<StormParam> l = (List<StormParam>) params.get(id);
1505                if (l == null) {
1506                        l = new ArrayList<StormParam>();
1507                        params.put(id, l);
1508                }
1509                return l;
1510        }
1511
1512        /**
1513         * _more_
1514         * 
1515         * @param way
1516         *            _more_
1517         * 
1518         * @return _more_
1519         */
1520        protected List<StormParam> getConeParams(WayDisplayState way) {
1521                if (way.getWay().isObservation()) {
1522                        return getParams(ID_OBS_CONE);
1523                }
1524                return getParams(ID_FORECAST_CONE);
1525        }
1526
1527        /**
1528         * _more_
1529         * 
1530         * @param way
1531         *            _more_
1532         * 
1533         * @return _more_
1534         */
1535        protected StormParam getRingsParam(WayDisplayState way) {
1536                if (way.getWay().isObservation()) {
1537                        return (StormParam) params.get(ID_OBS_RINGS);
1538                }
1539                return (StormParam) params.get(ID_FORECAST_RINGS);
1540        }
1541
1542        /**
1543         * _more_
1544         * 
1545         * @param way
1546         *            _more_
1547         * 
1548         * @return _more_
1549         */
1550        protected StormParam getColorParam(WayDisplayState way) {
1551                return getColorParam(way.getWay().isObservation());
1552        }
1553
1554        /**
1555         * _more_
1556         * 
1557         * @param forObs
1558         *            _more_
1559         * 
1560         * @return _more_
1561         */
1562        protected StormParam getColorParam(boolean forObs) {
1563                if (forObs) {
1564                        StormParam sp = (StormParam) params.get(ID_OBS_COLOR);
1565                        if (sp == null)
1566                                sp = getFixedParam();
1567                        return sp;
1568                }
1569                return (StormParam) params.get(ID_FORECAST_COLOR);
1570        }
1571
1572        /**
1573         * _more_
1574         * 
1575         * @throws Exception
1576         *             _more_
1577         */
1578        protected void updateCharts() throws Exception {
1579                if (mainContents == null) {
1580                        return;
1581                }
1582
1583                for (StormTrackChart stormTrackChart : charts) {
1584                        stormTrackChart.updateChart();
1585                }
1586        }
1587
1588        /**
1589         * _more_
1590         * 
1591         * @param displayState
1592         *            _more_
1593         * 
1594         * @throws Exception
1595         *             _more_
1596         */
1597        protected void displayStateChanged(DisplayState displayState)
1598                        throws Exception {
1599                updateDisplays();
1600                checkVisibility();
1601        }
1602
1603        /**
1604         * _more_
1605         * 
1606         * @param track
1607         *            _more_
1608         * 
1609         * @throws Exception
1610         *             _more_
1611         */
1612        protected void updateDisplays(StormTrack track) throws Exception {
1613                Way way = track.getWay();
1614                WayDisplayState wds = wayDisplayStateMap.get(way);
1615                if (wds != null) {
1616                        wds.updateDisplay(true);
1617                        for (StormTrackTableModel trackModel : tableModels) {
1618                                if (trackModel.getStormTrack().equals(track)) {
1619                                        trackModel.fireTableStructureChanged();
1620                                        Component comp = (Component) track
1621                                                        .getTemporaryProperty(PROP_TRACK_TABLE);
1622                                        track.setIsEdited(true);
1623                                        if (comp != null) {
1624                                                tableTreePanel.show(comp);
1625                                                tableTreePanel.showPath(comp);
1626                                        }
1627                                        break;
1628                                }
1629                        }
1630                }
1631        }
1632
1633        /**
1634         * _more_
1635         * 
1636         * @throws Exception
1637         *             _more_
1638         */
1639        protected void updateDisplays() throws Exception {
1640                updateDisplays(false);
1641        }
1642
1643        // sstretch protected void updateDisplays() throws Exception {
1644
1645        /**
1646         * _more_
1647         * 
1648         * @param force
1649         *            _more_
1650         * 
1651         * @throws Exception
1652         *             _more_
1653         */
1654        
1655        protected void updateDisplays(boolean force) throws Exception {
1656                DisplayMaster displayMaster = stormTrackControl.getDisplayMaster();
1657                boolean wasActive = displayMaster.isActive();
1658                displayMaster.setDisplayInactive();
1659                try {
1660                        List<WayDisplayState> wayDisplayStates = getWayDisplayStates();
1661                        for (WayDisplayState wds : wayDisplayStates) {
1662                                if (!okToShowWay(wds.getWay())) {
1663                                        continue;
1664                                }
1665                                wds.updateDisplay(force);
1666                        }
1667                } finally {
1668                        if (wasActive) {
1669                                try {
1670//                                      displayMaster.setActive(true);
1671                                    displayMaster.setDisplayActive();
1672                                } catch (Exception exc) {
1673                                }
1674                        }
1675                }
1676
1677                if (obsColorTableLabel != null) {
1678                        ColorTable ct = null;
1679
1680                        ct = getColorTable(getColorParam(true));
1681                        obsColorTableLabel.setIcon(((ct != null) ? ColorTableCanvas
1682                                        .getIcon(ct) : null));
1683
1684                        obsColorTableLabel.setToolTipText(getColorTableToolTip(true));
1685
1686                }
1687
1688                if (forecastColorTableLabel != null) {
1689                        ColorTable ct = null;
1690
1691                        ct = getColorTable(getColorParam(false));
1692                        forecastColorTableLabel.setIcon(((ct != null) ? ColorTableCanvas
1693                                        .getIcon(ct) : null));
1694
1695                        forecastColorTableLabel.setToolTipText(getColorTableToolTip(false));
1696                }
1697
1698        }
1699
1700        /**
1701         * _more_
1702         * 
1703         * @param forObs
1704         *            _more_
1705         * 
1706         * @return _more_
1707         */
1708        protected String getColorTableToolTip(boolean forObs) {
1709                StormParam param = getColorParam(forObs);
1710                if (param == null) {
1711                        return "Color table preview";
1712                }
1713                Range range = getStormTrackControl().getIdv().getParamDefaultsEditor()
1714                                .getParamRange(param.getName());
1715                if (range == null) {
1716                        return "Color table preview";
1717                }
1718
1719                Unit displayUnit = getStormTrackControl().getIdv()
1720                                .getParamDefaultsEditor().getParamDisplayUnit(param.getName());
1721
1722                String unit = ((displayUnit != null) ? "[" + displayUnit + "]" : "");
1723                return "Range: " + range.getMin() + unit + " - " + range.getMax()
1724                                + unit;
1725        }
1726
1727        /**
1728         * _more_
1729         * 
1730         * @param param
1731         *            _more_
1732         * 
1733         * @return _more_
1734         */
1735        protected ColorTable getColorTable(StormParam param) {
1736                if (param == null) {
1737
1738                        return null;
1739                } else if (param.getName().equalsIgnoreCase("Fixed")) {
1740                        try {
1741                                getStormTrackControl().getColorTableWidget(new Range(1.0, 1.0));
1742                        } catch (VisADException r) {
1743                        } catch (RemoteException s) {
1744                        }
1745                        return null;
1746                }
1747
1748                Range range = getStormTrackControl().getIdv().getParamDefaultsEditor()
1749                                .getParamRange(param.getName());
1750                if (range == null) {
1751                        range = new Range(1.0, 100.0);
1752                }
1753
1754                ColorTableWidget ctw = null;
1755                try {
1756                        if (colorRangeChanged) {
1757                                range = getStormTrackControl().getRangeForColorTable();
1758                        }
1759                        ctw = getStormTrackControl().getColorTableWidget(range);
1760                } catch (VisADException r) {
1761                } catch (RemoteException s) {
1762                }
1763                ColorTable ct = ctw.getColorTable();
1764                // getStormTrackControl().getIdv().getParamDefaultsEditor()
1765                // .getParamColorTable(param.getName(), false);
1766                if (ct == null) {
1767                        ct = getStormTrackControl().getColorTable();
1768                }
1769                // ct.setRange(range);
1770
1771                return ct;
1772        }
1773
1774        /**
1775         * _more_
1776         * 
1777         * @return _more_
1778         */
1779        protected StationModel getObsLayoutModel() {
1780                if ((obsLayoutModelName == null) || obsLayoutModelName.equals("none")) {
1781                        return null;
1782                }
1783
1784                StationModelManager smm = stormTrackControl.getControlContext()
1785                                .getStationModelManager();
1786                return smm.getStationModel(obsLayoutModelName);
1787                /*
1788                 * StationModel model = new StationModel("TrackLocation"); ShapeSymbol
1789                 * shapeSymbol = new ShapeSymbol(0, 0);
1790                 * shapeSymbol.setShape(ucar.visad.ShapeUtility.HURRICANE);
1791                 * shapeSymbol.setScale(2.0f); shapeSymbol.bounds = new
1792                 * java.awt.Rectangle(-15, -15, 30, 30);
1793                 * shapeSymbol.setRectPoint(Glyph.PT_MM);
1794                 * shapeSymbol.setForeground(null); model.addSymbol(shapeSymbol); return
1795                 * model;
1796                 */
1797        }
1798
1799        /**
1800         * _more_
1801         * 
1802         * @return _more_
1803         */
1804        protected StationModel getObsPointLayoutModel() {
1805                if ((obsPointLayoutModelName == null)
1806                                || obsPointLayoutModelName.equals("none")) {
1807                        return null;
1808                }
1809
1810                StationModelManager smm = stormTrackControl.getControlContext()
1811                                .getStationModelManager();
1812                return smm.getStationModel(obsPointLayoutModelName);
1813                /*
1814                 * StationModel model = new StationModel("TrackLocation"); ShapeSymbol
1815                 * shapeSymbol = new ShapeSymbol(0, 0);
1816                 * shapeSymbol.setShape(ucar.visad.ShapeUtility.HURRICANE);
1817                 * shapeSymbol.setScale(2.0f); shapeSymbol.bounds = new
1818                 * java.awt.Rectangle(-15, -15, 30, 30);
1819                 * shapeSymbol.setRectPoint(Glyph.PT_MM);
1820                 * shapeSymbol.setForeground(null); model.addSymbol(shapeSymbol); return
1821                 * model;
1822                 */
1823        }
1824
1825        /**
1826         * _more_
1827         * 
1828         * @return _more_
1829         */
1830        protected StationModel getForecastLayoutModel() {
1831                if ((forecastLayoutModelName == null)
1832                                || forecastLayoutModelName.equals("none")) {
1833                        return null;
1834                }
1835                StationModelManager smm = stormTrackControl.getControlContext()
1836                                .getStationModelManager();
1837                StationModel sm = smm.getStationModel(forecastLayoutModelName);
1838                if (sm != null) {
1839                        return sm;
1840                }
1841                StationModel model = new StationModel("TrackLocation");
1842                ShapeSymbol shapeSymbol = new ShapeSymbol(0, 0);
1843                shapeSymbol.setScale(0.3f);
1844                shapeSymbol.setShape(ucar.visad.ShapeUtility.CIRCLE);
1845                shapeSymbol.bounds = new java.awt.Rectangle(-15, -15, 30, 30);
1846                shapeSymbol.setRectPoint(Glyph.PT_MM);
1847                model.addSymbol(shapeSymbol);
1848                return model;
1849        }
1850
1851        // ucar.visad.Util.makeTimeField(List<Data> ranges, List times)
1852
1853        /**
1854         * Animation animation = stormTrackControl.getViewAnimation(); if (animation
1855         * == null) { return; } List<StormTrack> visibleTracks = new
1856         * ArrayList<StormTrack>(); Real currentAnimationTime =
1857         * animation.getAniValue(); if (currentAnimationTime == null ||
1858         * currentAnimationTime.isMissing()) { return; } Iterate way display states
1859         * boolean visible = false; if(wds.shouldShowTrack() &&
1860         * wds.hasTrackDisplay()) { FieldImpl field =
1861         * (FieldImplt)wds.getTrackDisplay().getData() if(field==null) continue; Set
1862         * timeSet = GridUtil.getTimeSet(); if(timeSet == null) continue; if
1863         * (timeSet.getLength() == 1) { visible = true; } else { //Else work the
1864         * visad magic float timeValueFloat = (float) currentAnimationTime.getValue(
1865         * timeSet.getSetUnits()[0]); // System.err.println("multiple times:" +
1866         * timeValueFloat); float[][] value = { { timeValueFloat } }; int[] index =
1867         * timeSet.valueToIndex(value); // System.err.println("index:" + index[0]);
1868         * visible = (index[0] >= 0); } if(visible) { //Find the closest track in
1869         * wds in time visibleTracks.add(..); } }
1870         * 
1871         * 
1872         * Now search in space
1873         * 
1874         * @param stormTrackChart
1875         *            _more_
1876         */
1877
1878        /**
1879         * _more_
1880         * 
1881         * @param stormTrackChart
1882         *            _more_
1883         */
1884        protected void removeChart(StormTrackChart stormTrackChart) {
1885                charts.remove(stormTrackChart);
1886                tabbedPane.remove(stormTrackChart.getContents());
1887        }
1888
1889        /**
1890         * _more_
1891         */
1892        public void addForecastTimeChart() {
1893                addForecastChart(StormTrackChart.MODE_FORECASTTIME);
1894        }
1895
1896        /**
1897         * _more_
1898         */
1899        public void addForecastHourChart() {
1900                addForecastChart(StormTrackChart.MODE_FORECASTHOUR);
1901        }
1902
1903        /**
1904         * _more_
1905         * 
1906         * @param mode
1907         *            _more_
1908         */
1909        public void addForecastChart(int mode) {
1910                String chartName = GuiUtils.getInput("Please enter a chart name",
1911                                "Chart Name: ", "Storm Chart");
1912                if (chartName == null) {
1913                        return;
1914                }
1915                StormTrackChart stormTrackChart = new StormTrackChart(this, chartName,
1916                                mode);
1917                charts.add(stormTrackChart);
1918                tabbedPane.addTab(stormTrackChart.getName(), stormTrackChart
1919                                .getContents());
1920                stormTrackChart.updateChart();
1921
1922        }
1923
1924        /**
1925         * _more_
1926         * 
1927         * @return _more_
1928         */
1929        public List<StormParam> getStormChartParams() {
1930                Hashtable<String, Boolean> s1 = stormTrackControl.getOkParams();
1931                List<StormParam> allParams = stormTrackControl.getTrackParams();
1932                List<StormParam> params = new ArrayList();
1933                for (StormParam sp : allParams) {
1934                        Boolean v = s1.get(sp.getName());
1935                        if ((v != null) && v.booleanValue()) {
1936                                params.add(sp);
1937                        }
1938                }
1939                return params;
1940                // return stormTrackControl.getChartParamFromSelector();
1941        }
1942
1943        /**
1944         * _more_
1945         * 
1946         * @return _more_
1947         */
1948        private JComponent getTrackTable() {
1949                final Font boldFont = new Font("Dialog", Font.BOLD, 10);
1950                final Font plainFont = new Font("Dialog", Font.PLAIN, 10);
1951                tableTreePanel = new TreePanel(true, 150) {
1952                        public DefaultTreeCellRenderer doMakeTreeCellRenderer() {
1953                                return new DefaultTreeCellRenderer() {
1954                                        public Component getTreeCellRendererComponent(
1955                                                        JTree theTree, Object value, boolean sel,
1956                                                        boolean expanded, boolean leaf, int row,
1957                                                        boolean hasFocus) {
1958                                                super.getTreeCellRendererComponent(theTree, value, sel,
1959                                                                expanded, leaf, row, hasFocus);
1960                                                if (!(value instanceof TreePanel.MyTreeNode)) {
1961                                                        return this;
1962                                                }
1963                                                TreePanel.MyTreeNode node = (TreePanel.MyTreeNode) value;
1964                                                StormTrack track = (StormTrack) node.getObject();
1965                                                if (track.getIsEdited()) {
1966                                                        this.setFont(boldFont);
1967                                                        this.setForeground(Color.red);
1968                                                } else {
1969                                                        this.setFont(plainFont);
1970                                                        this.setForeground(Color.black);
1971                                                }
1972                                                return this;
1973                                        }
1974                                };
1975                        }
1976                };
1977
1978                int width = 400;
1979                int height = 400;
1980                for (StormTrack track : trackCollection.getTracks()) {
1981                        final StormTrack theTrack = track;
1982                        StormTrackTableModel tableModel = new StormTrackTableModel(this,
1983                                        track);
1984                        tableModels.add(tableModel);
1985                        TableSorter sorter = new TableSorter(tableModel);
1986                        JTable trackTable = new JTable(sorter);
1987                        JTableHeader header = trackTable.getTableHeader();
1988                        header.setToolTipText("Click to sort");
1989                        sorter.setTableHeader(trackTable.getTableHeader());
1990
1991                        JScrollPane scroller = GuiUtils.makeScrollPane(trackTable, width,
1992                                        height);
1993                        scroller.setBorder(BorderFactory.createLoweredBevelBorder());
1994                        JComponent contents = scroller;
1995                        if (!track.getWay().isObservation()) {
1996                                contents = GuiUtils.topCenter(GuiUtils.left(GuiUtils.inset(
1997                                                new JLabel(track.getStartTime().toString()), 5)),
1998                                                contents);
1999                        }
2000
2001                        track.putTemporaryProperty(PROP_TRACK_TABLE, contents);
2002
2003                        JButton flythroughBtn = GuiUtils.makeButton("Fly through", this,
2004                                        "flythroughTrack", track);
2005                        contents = GuiUtils.centerBottom(contents, GuiUtils
2006                                        .right(flythroughBtn));
2007                        tableTreePanel.addComponent(contents, track.getWay().toString(),
2008                                        track.getStartTime().toString(), null, track);
2009                }
2010
2011                return tableTreePanel;
2012        }
2013
2014        /**
2015         * _more_
2016         * 
2017         * @param track
2018         *            _more_
2019         */
2020        public void flythroughTrack(StormTrack track) {
2021                try {
2022                        List<FlythroughPoint> points = new ArrayList<FlythroughPoint>();
2023                        for (StormTrackPoint stp : track.getTrackPoints()) {
2024                                EarthLocation newLoc = makePoint(stp.getLocation()
2025                                                .getLatitude().getValue(CommonUnit.degree), stp
2026                                                .getLocation().getLongitude().getValue(
2027                                                                CommonUnit.degree));
2028                                points.add(new FlythroughPoint(newLoc, stp.getTime()));
2029                        }
2030                        stormTrackControl.getMapViewManager().flythrough(points);
2031                } catch (Exception exc) {
2032                        stormTrackControl.logException("Doing flythrough", exc);
2033                }
2034        }
2035
2036        /** _more_ */
2037        public static final PatternFileFilter FILTER_DAT = new PatternFileFilter(
2038                        ".+\\.dat", "Diamond Format (*.dat)", ".dat");
2039
2040        /** _more_ */
2041        JCheckBox obsCbx = new JCheckBox("Observation", true);
2042
2043        /** _more_ */
2044        JCheckBox forecastCbx = new JCheckBox("Forecast", true);
2045
2046        /** _more_ */
2047        JCheckBox mostRecentCbx = new JCheckBox("Most Recent Forecasts", false);
2048
2049        /** _more_ */
2050        JCheckBox editedCbx = new JCheckBox("Edited Tracks", false);
2051
2052        /**
2053         * _more_
2054         */
2055        public void writeToDataFile() {
2056                try {
2057                        JComponent accessory = GuiUtils.top(GuiUtils.vbox(obsCbx,
2058                                        forecastCbx, mostRecentCbx, editedCbx));
2059
2060                        String filename = FileManager.getWriteFile(Misc.newList(
2061                                        FileManager.FILTER_XLS, FILTER_DAT),
2062                                        FileManager.SUFFIX_XLS, accessory);
2063                        if (filename == null) {
2064                                return;
2065                        }
2066
2067                        List<StormTrack> tracksToWrite = new ArrayList<StormTrack>();
2068                        List<Way> waysToUse = new ArrayList<Way>();
2069                        Hashtable<Way, List> trackMap = new Hashtable<Way, List>();
2070                        for (StormTrack track : trackCollection.getTracks()) {
2071                                List tracks = trackMap.get(track.getWay());
2072                                if (tracks == null) {
2073                                        tracks = new ArrayList();
2074                                        trackMap.put(track.getWay(), tracks);
2075                                        waysToUse.add(track.getWay());
2076                                }
2077                                tracks.add(track);
2078                                if (editedCbx.isSelected()) {
2079                                        if (track.getIsEdited()) {
2080                                                tracksToWrite.add(track);
2081                                        }
2082                                } else {
2083                                        if (track.getWay().isObservation()) {
2084                                                if (obsCbx.isSelected()) {
2085                                                        tracksToWrite.add(track);
2086                                                }
2087                                        } else {
2088                                                if (forecastCbx.isSelected()) {
2089                                                        tracksToWrite.add(track);
2090                                                }
2091
2092                                        }
2093                                }
2094
2095                        }
2096
2097                        if (filename.endsWith(".dat")) {
2098                                StringBuffer sb = StormTrack.toDiamond7(tracksToWrite,
2099                                                stormInfo.getStormId());
2100                                IOUtil.writeFile(filename, sb.toString());
2101                                return;
2102                        }
2103
2104                        Hashtable sheetNames = new Hashtable();
2105                        HSSFWorkbook wb = new HSSFWorkbook();
2106                        StormTrack obsTrack = trackCollection.getObsTrack();
2107                        // Write the obs track first
2108                        if ((obsTrack != null) && obsCbx.isSelected()) {
2109                                write(wb, obsTrack, sheetNames);
2110                        }
2111                        if (forecastCbx.isSelected()) {
2112                                waysToUse = Misc.sort(waysToUse);
2113                                for (Way way : waysToUse) {
2114                                        if (way.isObservation()) {
2115                                                continue;
2116                                        }
2117                                        List<StormTrack> tracks = (List<StormTrack>) Misc
2118                                                        .sort(trackMap.get(way));
2119                                        if (mostRecentCbx.isSelected()) {
2120                                                write(wb, tracks.get(tracks.size() - 1), sheetNames);
2121                                        } else {
2122                                                for (StormTrack track : tracks) {
2123                                                        write(wb, track, sheetNames);
2124                                                }
2125                                        }
2126                                }
2127                        }
2128                        FileOutputStream fileOut = new FileOutputStream(filename);
2129                        wb.write(fileOut);
2130                        fileOut.close();
2131                } catch (Exception exc) {
2132                        stormTrackControl.logException("Writing spreadsheet", exc);
2133                }
2134        }
2135
2136        /**
2137         * _more_
2138         * 
2139         * @param wb
2140         *            _more_
2141         * @param track
2142         *            _more_
2143         * @param sheetNames
2144         *            _more_
2145         */
2146        protected void write(HSSFWorkbook wb, StormTrack track, Hashtable sheetNames) {
2147                int cnt = 0;
2148                String dateString = track.getStartTime().formattedString(
2149                                "yyyy-MM-dd hhmm", DateUtil.TIMEZONE_GMT);
2150                String sheetName = track.getWay() + " - " + dateString;
2151                if (sheetName.length() > 30) {
2152                        sheetName = sheetName.substring(0, 29);
2153                }
2154                // The sheet name length is limited
2155                while (sheetNames.get(sheetName) != null) {
2156                        sheetName = (cnt++) + " " + sheetName;
2157                        if (sheetName.length() > 30) {
2158                                sheetName = sheetName.substring(0, 29);
2159                        }
2160                }
2161                sheetNames.put(sheetName, sheetName);
2162                HSSFSheet sheet = wb.createSheet(sheetName);
2163
2164                int rowCnt = 0;
2165                List<StormParam> params = track.getParams();
2166                HSSFCell cell;
2167                HSSFRow row;
2168
2169                for (StormTrackPoint stp : track.getTrackPoints()) {
2170                        if (rowCnt == 0) {
2171                                row = sheet.createRow((short) rowCnt++);
2172                                row.createCell(0).setCellValue(new HSSFRichTextString("Time"));
2173                                row.createCell(1).setCellValue(new HSSFRichTextString("Latitude"));
2174                                row.createCell(2).setCellValue(new HSSFRichTextString("Longitude"));
2175                                for (int colIdx = 0; colIdx < params.size(); colIdx++) {
2176                                        row.createCell((colIdx + 3)).setCellValue(new HSSFRichTextString(
2177                                                        params.get(colIdx).toString()));
2178                                }
2179                        }
2180                        row = sheet.createRow((short) rowCnt++);
2181                        row.createCell(0).setCellValue(new HSSFRichTextString(stp.getTime().toString()));
2182                        row.createCell(1).setCellValue(
2183                                        stp.getLocation().getLatitude().getValue());
2184                        row.createCell(2).setCellValue(
2185                                        stp.getLocation().getLongitude().getValue());
2186                        for (int colIdx = 0; colIdx < params.size(); colIdx++) {
2187                                Real r = stp.getAttribute(params.get(colIdx));
2188                                cell = row.createCell((colIdx + 3));
2189                                cell.setCellValue(r.getValue());
2190                        }
2191                }
2192        }
2193
2194        /**
2195         * _more_
2196         * 
2197         * @param docNode
2198         *            _more_
2199         * @param state
2200         *            _more_
2201         * @param doObs
2202         *            _more_
2203         * @param doForecast
2204         *            _more_
2205         * @param mostRecent
2206         *            _more_
2207         * 
2208         * @throws RemoteException
2209         *             _more_
2210         * @throws VisADException
2211         *             _more_
2212         */
2213        public void writeToKml(Element docNode, Hashtable state, boolean doObs,
2214                        boolean doForecast, boolean mostRecent) throws VisADException,
2215                        RemoteException {
2216                try {
2217                        List<Way> waysToUse = new ArrayList<Way>();
2218                        Hashtable<Way, List> trackMap = new Hashtable<Way, List>();
2219                        for (StormTrack track : trackCollection.getTracks()) {
2220                                List tracks = trackMap.get(track.getWay());
2221                                if (tracks == null) {
2222                                        tracks = new ArrayList();
2223                                        trackMap.put(track.getWay(), tracks);
2224                                        waysToUse.add(track.getWay());
2225                                }
2226                                tracks.add(track);
2227                        }
2228
2229                        Element topFolder = KmlUtil.folder(docNode, "Storm: "
2230                                        + stormInfo.toString()
2231                                        + "   "
2232                                        + stormInfo.getStartTime().formattedString("yyyy-MM-dd",
2233                                                        DateUtil.TIMEZONE_GMT));
2234                        StormTrack obsTrack = trackCollection.getObsTrack();
2235                        // Write the obs track first
2236                        if ((obsTrack != null) && doObs) {
2237                                Element obsFolder = KmlUtil.folder(topFolder, "Observation");
2238                                stormTrackControl.writeToGE(docNode, state, obsFolder,
2239                                                obsTrack, getWayDisplayState(obsTrack.getWay())
2240                                                                .getColor());
2241                        }
2242                        if (doForecast) {
2243                                waysToUse = Misc.sort(waysToUse);
2244                                for (Way way : waysToUse) {
2245                                        if (way.isObservation()) {
2246                                                continue;
2247                                        }
2248                                        Element wayNode = KmlUtil.folder(topFolder,
2249                                                        stormTrackControl.getWayName() + ": " + way);
2250                                        List<StormTrack> tracks = (List<StormTrack>) Misc
2251                                                        .sort(trackMap.get(way));
2252                                        if (mostRecent) {
2253                                                StormTrack recent = tracks.get(tracks.size() - 1);
2254                                                stormTrackControl.writeToGE(docNode, state, wayNode,
2255                                                                recent, getWayDisplayState(recent.getWay())
2256                                                                                .getColor());
2257                                        } else {
2258                                                for (StormTrack track : tracks) {
2259                                                        stormTrackControl.writeToGE(docNode, state,
2260                                                                        wayNode, track, getWayDisplayState(
2261                                                                                        track.getWay()).getColor());
2262
2263                                                }
2264                                        }
2265                                }
2266                        }
2267
2268                } catch (Exception exc) {
2269                        stormTrackControl.logException("Writing KML", exc);
2270                }
2271        }
2272
2273        /**
2274         * Set the StormInfo property.
2275         * 
2276         * @param value
2277         *            The new value for StormInfo
2278         */
2279        public void setStormInfo(StormInfo value) {
2280                stormInfo = value;
2281        }
2282
2283        /**
2284         * Get the StormInfo property.
2285         * 
2286         * @return The StormInfo
2287         */
2288        public StormInfo getStormInfo() {
2289                return stormInfo;
2290        }
2291
2292        /**
2293         * Set the Changed property.
2294         * 
2295         * @param value
2296         *            The new value for Changed
2297         */
2298        public void setChanged(boolean value) {
2299                changed = value;
2300        }
2301
2302        /**
2303         * Get the Changed property.
2304         * 
2305         * @return The Changed
2306         */
2307        public boolean getChanged() {
2308                return changed;
2309        }
2310
2311        /**
2312         * Set the Active property.
2313         * 
2314         * @param value
2315         *            The new value for Active
2316         */
2317        public void setActive(boolean value) {
2318                active = value;
2319        }
2320
2321        /**
2322         * Get the Active property.
2323         * 
2324         * @return The Active
2325         */
2326        public boolean getActive() {
2327                return active;
2328        }
2329
2330        /**
2331         * Set the WayDisplayStateMap property.
2332         * 
2333         * @param value
2334         *            The new value for WayDisplayStateMap
2335         */
2336        public void setWayDisplayStateMap(Hashtable<Way, WayDisplayState> value) {
2337                wayDisplayStateMap = value;
2338        }
2339
2340        /**
2341         * Get the WayDisplayStateMap property.
2342         * 
2343         * @return The WayDisplayStateMap
2344         */
2345        public Hashtable<Way, WayDisplayState> getWayDisplayStateMap() {
2346                return wayDisplayStateMap;
2347        }
2348
2349        /**
2350         * Set the ForecastState property.
2351         * 
2352         * @param value
2353         *            The new value for ForecastState
2354         */
2355        public void setForecastState(WayDisplayState value) {
2356                forecastState = value;
2357        }
2358
2359        /**
2360         * Get the ForecastState property.
2361         * 
2362         * @return The ForecastState
2363         */
2364        public WayDisplayState getObservationState() {
2365                return obsDisplayState;
2366        }
2367
2368        /**
2369         * Set the ForecastState property.
2370         * 
2371         * @param value
2372         *            The new value for ForecastState
2373         */
2374        public void setObservationState(WayDisplayState value) {
2375                obsDisplayState = value;
2376        }
2377
2378        /**
2379         * Get the ForecastState property.
2380         * 
2381         * @return The ForecastState
2382         */
2383        public WayDisplayState getForecastState() {
2384                return forecastState;
2385        }
2386
2387        /**
2388         * Cycle through the color list.
2389         * 
2390         * 
2391         * @param nextColor
2392         *            _more_
2393         * @return The next color in the list
2394         */
2395        public static Color getNextColor(int[] nextColor) {
2396                if (nextColor[0] >= colors.length) {
2397                        nextColor[0] = 0;
2398                }
2399                return colors[nextColor[0]++];
2400        }
2401
2402        /**
2403         * Set the Charts property.
2404         * 
2405         * @param value
2406         *            The new value for Charts
2407         */
2408        public void setCharts(List<StormTrackChart> value) {
2409                charts = value;
2410        }
2411
2412        /**
2413         * Get the Charts property.
2414         * 
2415         * @return The Charts
2416         */
2417        public List<StormTrackChart> getCharts() {
2418                return charts;
2419        }
2420
2421        /**
2422         * Set the Params property.
2423         * 
2424         * @param value
2425         *            The new value for Params
2426         */
2427        public void setParams(Hashtable value) {
2428                params = value;
2429        }
2430
2431        /**
2432         * Get the Params property.
2433         * 
2434         * @return The Params
2435         */
2436        public Hashtable getParams() {
2437                return params;
2438        }
2439
2440        /**
2441         * Set the ObsLayoutModelName property.
2442         * 
2443         * @param value
2444         *            The new value for ObsLayoutModelName
2445         */
2446        public void setObsLayoutModelName(String value) {
2447                obsLayoutModelName = value;
2448        }
2449
2450        /**
2451         * Get the ObsLayoutModelName property.
2452         * 
2453         * @return The ObsLayoutModelName
2454         */
2455        public String getObsLayoutModelName() {
2456                return obsLayoutModelName;
2457        }
2458
2459        /**
2460         * Set the ObsLayoutModelName property.
2461         * 
2462         * @param value
2463         *            The new value for ObsLayoutModelName
2464         */
2465        public void setObsPointLayoutModelName(String value) {
2466                obsPointLayoutModelName = value;
2467        }
2468
2469        /**
2470         * Get the ObsLayoutModelName property.
2471         * 
2472         * @return The ObsLayoutModelName
2473         */
2474        public String getObsPointLayoutModelName() {
2475                return obsPointLayoutModelName;
2476        }
2477
2478        /**
2479         * Set the ForecastLayoutModelName property.
2480         * 
2481         * @param value
2482         *            The new value for ForecastLayoutModelName
2483         */
2484        public void setForecastLayoutModelName(String value) {
2485                forecastLayoutModelName = value;
2486        }
2487
2488        /**
2489         * Get the ForecastLayoutModelName property.
2490         * 
2491         * @return The ForecastLayoutModelName
2492         */
2493        public String getForecastLayoutModelName() {
2494                return forecastLayoutModelName;
2495        }
2496
2497        /**
2498         * _more_
2499         * 
2500         * @return _more_
2501         */
2502        public int getForecastAnimationMode() {
2503                return forecastAnimationMode;
2504        }
2505
2506        /**
2507         * _more_
2508         * 
2509         * @param value
2510         *            _more_
2511         */
2512        public void setForecastAnimationMode(int value) {
2513                forecastAnimationMode = value;
2514        }
2515
2516        /**
2517         * _more_
2518         */
2519        public void markHasBeenEdited() {
2520                hasBeenEdited = true;
2521        }
2522
2523        public void colorRangeChanged() {
2524                DisplayMaster displayMaster = stormTrackControl.getDisplayMaster();
2525                colorRangeChanged = true;
2526                displayMaster.setDisplayInactive();
2527                try {
2528                        stormTrackControl.stormChanged(StormDisplayState.this);
2529                        updateDisplays();
2530                } catch (Exception exc) {
2531                        stormTrackControl.logException("Changing color table", exc);
2532                }
2533
2534        }
2535
2536        /**
2537         * _more_
2538         */
2539        public boolean isColorRangeChanged() {
2540                return colorRangeChanged;
2541        }
2542}