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