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