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