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