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.Dimension;
033    import java.awt.Rectangle;
034    import java.awt.geom.Rectangle2D;
035    import java.rmi.RemoteException;
036    import java.text.DecimalFormat;
037    import java.util.ArrayList;
038    import java.util.Calendar;
039    import java.util.Date;
040    import java.util.Iterator;
041    import java.util.List;
042    import java.util.Vector;
043    
044    import javax.swing.JComponent;
045    
046    import ucar.unidata.data.grid.GridUtil;
047    import ucar.unidata.data.point.PointOb;
048    import ucar.unidata.data.point.PointObFactory;
049    import ucar.unidata.data.storm.StormParam;
050    import ucar.unidata.data.storm.StormTrack;
051    import ucar.unidata.data.storm.StormTrackPoint;
052    import ucar.unidata.data.storm.Way;
053    import ucar.unidata.geoloc.Bearing;
054    import ucar.unidata.geoloc.LatLonPoint;
055    import ucar.unidata.geoloc.LatLonPointImpl;
056    import ucar.unidata.geoloc.ProjectionPoint;
057    import ucar.unidata.geoloc.ProjectionPointImpl;
058    import ucar.unidata.geoloc.projection.FlatEarth;
059    import ucar.unidata.geoloc.projection.LatLonProjection;
060    import ucar.unidata.gis.SpatialGrid;
061    import ucar.unidata.ui.colortable.ColorTableDefaults;
062    import ucar.unidata.ui.symbol.StationModel;
063    import ucar.unidata.util.ColorTable;
064    import ucar.unidata.util.GuiUtils;
065    import ucar.unidata.util.LogUtil;
066    import ucar.unidata.util.Misc;
067    import ucar.unidata.util.Range;
068    import ucar.unidata.view.geoloc.NavigatedDisplay;
069    import ucar.visad.Util;
070    import ucar.visad.display.CompositeDisplayable;
071    import ucar.visad.display.Displayable;
072    import ucar.visad.display.StationModelDisplayable;
073    import ucar.visad.display.TrackDisplayable;
074    import visad.CommonUnit;
075    import visad.Data;
076    import visad.DateTime;
077    import visad.FieldImpl;
078    import visad.FunctionType;
079    import visad.Integer1DSet;
080    import visad.Real;
081    import visad.RealType;
082    import visad.Set;
083    import visad.SetType;
084    import visad.TextType;
085    import visad.Tuple;
086    import visad.Unit;
087    import visad.VisADException;
088    import visad.georef.EarthLocation;
089    import visad.georef.EarthLocationLite;
090    
091    /**
092     * 
093     * @author Unidata Development Team
094     * @version $Revision$
095     */
096    
097    public class WayDisplayState {
098    
099            /** Type for Azimuth */
100            private final RealType azimuthType = RealType.getRealType("Azimuth",
101                            CommonUnit.degree);
102    
103            /** _more_ */
104            private Way way;
105    
106            /** _more_ */
107            private StormDisplayState stormDisplayState;
108    
109            /** _more_ */
110            private DisplayState trackState;
111    
112            /** _more_ */
113            private DisplayState coneState;
114    
115            /** _more_ */
116            private DisplayState wayState;
117    
118            /** _more_ */
119            private DisplayState ringsState;
120    
121            /** _more_ */
122            List<PointOb> pointObs = new ArrayList<PointOb>();
123    
124            /** _more_ */
125            List<PointOb> allPointObs = new ArrayList<PointOb>();
126    
127            /** _more_ */
128            private List<StormTrack> tracks = new ArrayList<StormTrack>();
129    
130            /** _more_ */
131            private List<DateTime> times = new ArrayList<DateTime>();
132    
133            /** _more_ */
134            private Color color;
135    
136            /** _more_ */
137            private GuiUtils.ColorSwatch colorSwatch;
138    
139            /** _more_ */
140            private CompositeDisplayable holder;
141    
142            /** _more_ */
143            private StationModelDisplayable labelDisplay;
144    
145            /** _more_ */
146            private StationModelDisplayable obsPointDisplay;
147    
148            /** _more_ */
149            private TrackDisplayable trackDisplay;
150    
151            /** _more_ */
152            private TrackDisplayable ringsDisplay;
153    
154            /** _more_ */
155            private CompositeDisplayable conesHolder;
156    
157            /** _more_ */
158            private List<StormParam> coneParams;
159    
160            /** _more_ */
161            private StormParam ringsParam;
162    
163            /** _more_ */
164            private StormParam colorParam;
165    
166            /** _more_ */
167            private int modeParam = 99;
168    
169            /**
170             * _more_
171             */
172            public WayDisplayState() {
173            }
174    
175            /**
176             * _more_
177             * 
178             * 
179             * @param stormDisplayState
180             *            _more_
181             * @param way
182             *            _more_
183             */
184            public WayDisplayState(StormDisplayState stormDisplayState, Way way) {
185                    this.stormDisplayState = stormDisplayState;
186                    this.way = way;
187                    wayState = new DisplayState(this, "Show/Hide All", true);
188                    trackState = new DisplayState(this, "Show/Hide Track", true);
189                    coneState = new DisplayState(this, "Show/Hide Cone", false);
190                    ringsState = new DisplayState(this, "Show/Hide Rings", false);
191            }
192    
193            /**
194             * _more_
195             * 
196             * @return _more_
197             * 
198             * @throws RemoteException
199             *             _more_
200             * @throws VisADException
201             *             _more_
202             */
203            protected CompositeDisplayable getHolder() throws VisADException,
204                            RemoteException {
205                    if (holder == null) {
206                            holder = new CompositeDisplayable("way  holder");
207                            stormDisplayState.addDisplayable(holder);
208                    }
209                    return holder;
210            }
211    
212            /**
213             * _more_
214             * 
215             * @return _more_
216             */
217            public boolean hasTrackDisplay() {
218                    return trackDisplay != null;
219            }
220    
221            /**
222             * _more_
223             * 
224             * @return _more_
225             */
226            public boolean hasLabelDisplay() {
227                    return labelDisplay != null;
228            }
229    
230            /**
231             * _more_
232             * 
233             * @throws RemoteException
234             *             _more_
235             * @throws VisADException
236             *             _more_
237             */
238            private void removeTrackDisplay() throws VisADException, RemoteException {
239                    if (trackDisplay != null) {
240                            removeDisplayable(trackDisplay);
241                            trackDisplay = null;
242                    }
243            }
244    
245            /**
246             * _more_
247             * 
248             * @throws RemoteException
249             *             _more_
250             * @throws VisADException
251             *             _more_
252             */
253            private void removeLabelDisplay() throws VisADException, RemoteException {
254                    if (labelDisplay != null) {
255                            removeDisplayable(labelDisplay);
256                            labelDisplay = null;
257                    }
258            }
259    
260            /**
261             * _more_
262             * 
263             * @throws RemoteException
264             *             _more_
265             * @throws VisADException
266             *             _more_
267             */
268            private void removeObsPointDisplay() throws VisADException, RemoteException {
269                    if (obsPointDisplay != null) {
270                            removeDisplayable(obsPointDisplay);
271                            obsPointDisplay = null;
272                    }
273            }
274    
275            /**
276             * _more_
277             * 
278             * @return _more_
279             */
280            public boolean hasObsPointDisplay() {
281                    return obsPointDisplay != null;
282            }
283    
284            /**
285             * _more_
286             * 
287             * @return _more_
288             */
289            public boolean hasRingsDisplay() {
290                    return ringsDisplay != null;
291            }
292    
293            /**
294             * _more_
295             * 
296             * @return _more_
297             */
298            public boolean hasConeDisplay() {
299                    return conesHolder != null;
300            }
301    
302            /**
303             * _more_
304             * 
305             * 
306             * @param force
307             *            _more_
308             * @throws Exception
309             *             _more_
310             */
311            public void updateDisplay(boolean force) throws Exception {
312    
313                    if (!shouldShow()) {
314                            if (holder != null) {
315                                    holder.setVisible(false);
316                            }
317                            return;
318                    }
319    
320                    getHolder().setVisible(true);
321                    int forecastAnimationMode = stormDisplayState
322                                    .getForecastAnimationMode();
323                    if (shouldShowTrack()) {
324                            StormParam tmpParam = stormDisplayState.getColorParam(this);
325                            boolean hadTrack = hasTrackDisplay();
326                            boolean paramChanged = !Misc.equals(colorParam, tmpParam);
327                            boolean modeChanged = !(modeParam == forecastAnimationMode);
328                            if (force || !hadTrack || paramChanged || modeChanged
329                                            || stormDisplayState.isColorRangeChanged()) {
330                                    // System.err.println("makeing field");
331                                    colorParam = tmpParam;
332                                    // modeParam = forecastAnimationMode;
333                                    FieldImpl trackField = makeTrackField(forecastAnimationMode);
334                                    if (trackField != null) {
335                                            if (paramChanged) {
336                                                    trackDisplay = null;
337                                                    initTrackDisplay();
338                                            }
339                                            getTrackDisplay().setUseTimesInAnimation(false);
340                                            getTrackDisplay().setTrack(trackField);
341                                            Range range = null;
342                                            if (colorParam != null) {
343                                                    String paramName = colorParam.getName();
344                                                    range = stormDisplayState.getStormTrackControl()
345                                                                    .getIdv().getParamDefaultsEditor()
346                                                                    .getParamRange(paramName);
347                                                    if (stormDisplayState.isColorRangeChanged()) {
348                                                            range = stormDisplayState.getStormTrackControl()
349                                                                            .getRangeForColorTable();
350                                                            stormDisplayState.getStormTrackControl()
351                                                                            .getColorTableWidget(range);
352                                                    }
353    
354                                                    Unit displayUnit = stormDisplayState
355                                                                    .getStormTrackControl().getIdv()
356                                                                    .getParamDefaultsEditor().getParamDisplayUnit(
357                                                                                    paramName);
358                                                    if (displayUnit != null) {
359                                                            getTrackDisplay().setDisplayUnit(displayUnit);
360                                                    } else {
361                                                            Unit[] u = GridUtil.getParamUnits(trackField);
362                                                            if (u[0] != null) {
363                                                                    getTrackDisplay().setDisplayUnit(u[0]);
364                                                            }
365                                                    }
366                                            }
367                                            if (range == null) {
368                                                    range = GridUtil.getMinMax(trackField)[0];
369                                            }
370                                            getTrackDisplay().setRangeForColor(range.getMin(),
371                                                            range.getMax());
372                                    }
373                            }
374                            setTrackColor();
375                            getTrackDisplay().setVisible(true);
376                    } else {
377                            if (hasTrackDisplay()) {
378                                    getTrackDisplay().setVisible(false);
379                            }
380                    }
381    
382                    updateLayoutModel();
383    
384                    if (shouldShowCone()) {
385                            List<StormParam> tmp = stormDisplayState.getConeParams(this);
386                            if (!hasConeDisplay() || !Misc.equals(tmp, coneParams)
387                                            || !(modeParam == forecastAnimationMode)) {
388                                    this.coneParams = tmp;
389                                    getConesHolder().clearDisplayables();
390                                    setConeColor();
391                                    for (StormParam param : coneParams) {
392                                            TrackDisplayable coneDisplay = makeConeDisplay(param,
393                                                            forecastAnimationMode);
394                                            if (coneDisplay != null) {
395                                                    getConesHolder().addDisplayable(coneDisplay);
396                                            }
397                                    }
398                                    setConeColor();
399                            }
400                            getConesHolder().setVisible(true);
401                    } else {
402                            if (hasConeDisplay()) {
403                                    getConesHolder().setVisible(false);
404                            }
405                    }
406    
407                    if (shouldShowRings()) {
408                            StormParam tmp = stormDisplayState.getRingsParam(this);
409                            TrackDisplayable ringDisplay = getRingsDisplay();
410                            if (!hasRingsDisplay() || !Misc.equals(tmp, ringsParam)
411                                            || !(modeParam == forecastAnimationMode)) {
412                                    this.ringsParam = tmp;
413                                    setRingsColor();
414                                    FieldImpl field = makeRingsField(ringsParam,
415                                                    forecastAnimationMode);
416                                    if ((field == null) || (field.getLength() == 0)) {
417                                            ringDisplay.setData(new Real(0));
418                                    } else {
419                                            ringDisplay.setTrack(field);
420                                    }
421                                    setRingsColor();
422                            }
423                            ringsDisplay.setVisible(true);
424                    } else {
425                            if (hasRingsDisplay()) {
426                                    getRingsDisplay().setVisible(false);
427                            }
428                    }
429    
430                    modeParam = forecastAnimationMode;
431    
432            }
433    
434            /**
435             * _more_
436             * 
437             * @return _more_
438             */
439            public boolean shouldShow() {
440                    if (tracks.size() == 0) {
441                            return false;
442                    }
443                    if (!way.isObservation()
444                                    && !stormDisplayState.getForecastState().getWayState()
445                                                    .getVisible()) {
446                            return false;
447                    }
448                    // return visible;
449                    return wayState.getVisible();
450            }
451    
452            /**
453             * _more_
454             * 
455             * @return _more_
456             */
457            public boolean shouldShowTrack() {
458                    if (!way.isObservation()
459                                    && !stormDisplayState.getForecastState().getTrackState()
460                                                    .getVisible()) {
461                            return false;
462                    }
463                    return shouldShow() && trackState.getVisible();
464            }
465    
466            /**
467             * _more_
468             * 
469             * @return _more_
470             */
471            public boolean shouldShowRings() {
472                    if (!way.isObservation()
473                                    && !stormDisplayState.getForecastState().getRingsState()
474                                                    .getVisible()) {
475                            return false;
476                    }
477                    return shouldShow() && ringsState.getVisible();
478            }
479    
480            /**
481             * _more_
482             * 
483             * @return _more_
484             */
485            public boolean shouldShowCone() {
486                    if (!way.isObservation()
487                                    && !stormDisplayState.getForecastState().getConeState()
488                                                    .getVisible()) {
489                            return false;
490                    }
491                    return shouldShow() && coneState.getVisible();
492            }
493    
494            /**
495             * _more_
496             * 
497             * 
498             * @throws Exception
499             *             _more_
500             */
501            public void updateLayoutModel() throws Exception {
502                    StationModel sm;
503                    // If we are showing the track then create (if needed) the station model
504                    // displays
505                    if (shouldShowTrack()) {
506                            if (way.isObservation()) {
507                                    sm = stormDisplayState.getObsPointLayoutModel();
508                                    // We won't create them (or will remove them) if the layout
509                                    // model is null
510                                    if (sm == null) {
511                                            removeObsPointDisplay();
512                                    } else {
513                                            if (true) { // (!hasObsPointDisplay()) {
514                                                    FieldImpl pointField;
515                                                    pointField = PointObFactory.makeTimeSequenceOfPointObs(
516                                                                    allPointObs, -1, -1);
517    
518                                                    FieldImpl pointField1 = doDeclutter(pointField, sm);
519                                                    getObsPointDisplay().setStationData(pointField1);
520    
521                                            }
522                                            if (hasObsPointDisplay()) { // && !Misc.equals(sm,
523                                                                                                    // getObsPointDisplay().getStationModel()))
524                                                                                                    // {
525                                                    getObsPointDisplay().setStationModel(sm);
526                                            }
527                                    }
528                            }
529    
530                            sm = (way.isObservation() ? stormDisplayState.getObsLayoutModel()
531                                            : stormDisplayState.getForecastLayoutModel());
532                            if (sm == null) {
533                                    removeLabelDisplay();
534                            } else {
535                                    if (pointObs.size() > 0) { // (!hasLabelDisplay()) {
536                                            FieldImpl pointField = PointObFactory
537                                                            .makeTimeSequenceOfPointObs(pointObs, -1, -1);
538    
539                                            getLabelDisplay().setStationData(pointField);
540                                            getLabelDisplay().setStationModel(sm);
541                                    }
542                            }
543                    }
544    
545                    setLabelColor();
546                    if (hasObsPointDisplay()) {
547                            getObsPointDisplay().setVisible(shouldShowTrack());
548                    }
549    
550                    if (hasLabelDisplay()) {
551                            getLabelDisplay().setVisible(shouldShowTrack());
552                    }
553    
554            }
555    
556            /**
557             * Declutters the observations. This is just a wrapper around the real
558             * decluttering in doTheActualDecluttering(FieldImpl) to handle the case
559             * where there is a time sequence of observations.
560             * 
561             * @param obs
562             *            initial field of observations.
563             * @param sModel
564             *            _more_
565             * 
566             * @return a decluttered version of obs
567             * 
568             * @throws RemoteException
569             *             Java RMI error
570             * @throws VisADException
571             *             VisAD Error
572             */
573            private FieldImpl doDeclutter(FieldImpl obs, StationModel sModel)
574                            throws VisADException, RemoteException {
575    
576                    // long millis = System.currentTimeMillis();
577                    boolean isTimeSequence = GridUtil.isTimeSequence(obs);
578                    FieldImpl declutteredField = null;
579                    if (isTimeSequence) {
580                            Set timeSet = obs.getDomainSet();
581                            declutteredField = new FieldImpl((FunctionType) obs.getType(),
582                                            timeSet);
583                            int numTimes = timeSet.getLength();
584                            for (int i = 0; i < numTimes; i++) {
585                                    FieldImpl oneTime = (FieldImpl) obs.getSample(i);
586                                    FieldImpl subTime = doTheActualDecluttering(oneTime, sModel);
587                                    if (subTime != null) {
588                                            declutteredField.setSample(i, subTime, false);
589                                    }
590                            }
591                    } else {
592                            declutteredField = doTheActualDecluttering(obs, sModel);
593                    }
594                    // System.out.println("Subsetting took : " +
595                    // (System.currentTimeMillis() - millis) + " ms");
596                    return declutteredField;
597            }
598    
599            /**
600             * a * Declutters a single timestep of observations.
601             * 
602             * @param pointObs
603             *            point observations for one timestep.
604             * 
605             * @return a decluttered version of pointObs
606             * 
607             * @throws RemoteException
608             *             Java RMI error
609             * @throws VisADException
610             *             VisAD Error
611             */
612    
613            /** grid for decluttering */
614            private SpatialGrid stationGrid;
615    
616            /**
617             * _more_
618             * 
619             * @param pointObs
620             *            _more_
621             * @param sm
622             *            _more_
623             * 
624             * @return _more_
625             * 
626             * @throws RemoteException
627             *             _more_
628             * @throws VisADException
629             *             _more_
630             */
631            private FieldImpl doTheActualDecluttering(FieldImpl pointObs,
632                            StationModel sm) throws VisADException, RemoteException {
633                    if ((pointObs == null) || pointObs.isMissing()) {
634                            return pointObs;
635                    }
636                    FieldImpl retField = null;
637                    Set domainSet = pointObs.getDomainSet();
638                    int numObs = domainSet.getLength();
639                    Vector v = new Vector();
640    
641                    long t1 = System.currentTimeMillis();
642                    Rectangle glyphBounds = sm.getBounds();
643                    float myScale = getObsPointDisplay().getScale() * .0025f
644                                    * getDeclutterFilter();
645                    // System.out.println("\ndecluttering  myScale=" + myScale +
646                    // " filter=" +getDeclutterFilter());
647                    Rectangle2D scaledGlyphBounds = new Rectangle2D.Double(glyphBounds
648                                    .getX()
649                                    * myScale, glyphBounds.getY() * myScale, glyphBounds.getWidth()
650                                    * myScale, glyphBounds.getHeight() * myScale);
651                    NavigatedDisplay navDisplay = stormDisplayState.getStormTrackControl()
652                                    .getNavigatedDisplay();
653    
654                    Rectangle2D.Double obBounds = new Rectangle2D.Double();
655                    obBounds.width = scaledGlyphBounds.getWidth();
656                    obBounds.height = scaledGlyphBounds.getHeight();
657    
658                    if (stationGrid == null) {
659                            stationGrid = new SpatialGrid(200, 200);
660                    }
661                    stationGrid.clear();
662                    stationGrid.setGrid(getBounds(), scaledGlyphBounds);
663                    if (getDeclutterFilter() < 0.3f) {
664                            // stationGrid.setOverlap((int)((1.0-getDeclutterFilter())*100));
665                            // stationGrid.setOverlap( (int)((.5f-getDeclutterFilter())*100));
666                    } else {
667                            // stationGrid.setOverlap(0);
668                    }
669    
670                    double[] xyz = new double[3];
671                    // TODO: The repeated getSpatialCoords is a bit expensive
672                    for (int i = 0; i < numObs; i++) {
673                            PointOb ob = (PointOb) pointObs.getSample(i);
674                            xyz = navDisplay.getSpatialCoordinates(ob.getEarthLocation(), xyz);
675                            obBounds.x = xyz[0];
676                            obBounds.y = xyz[1];
677                            if (stationGrid.markIfClear(obBounds, "")) {
678                                    v.add(ob); // is in the bounds
679                            }
680                    }
681                    // stationGrid.print();
682    
683                    if (v.isEmpty()) {
684                            retField = new FieldImpl((FunctionType) pointObs.getType(),
685                                            new Integer1DSet(((SetType) domainSet.getType())
686                                                            .getDomain(), 1));
687                            retField.setSample(0, pointObs.getSample(0), false);
688                    } else if (v.size() == numObs) {
689                            retField = pointObs; // all were in domain, just return input
690                    } else {
691                            retField = new FieldImpl((FunctionType) pointObs.getType(),
692                                            new Integer1DSet(((SetType) domainSet.getType())
693                                                            .getDomain(), v.size()));
694                            retField.setSamples((PointOb[]) v.toArray(new PointOb[v.size()]),
695                                            false, false);
696                    }
697    
698                    return retField;
699            }
700    
701            /** decluttering filter factor */
702            private float declutterFilter = 1.0f;
703    
704            /**
705             * _more_
706             * 
707             * @return _more_
708             */
709            public float getDeclutterFilter() {
710                    return declutterFilter;
711            }
712    
713            /**
714             * _more_
715             * 
716             * @return _more_
717             */
718            protected Rectangle2D getBounds() {
719                    return calculateRectangle();
720            }
721    
722            /**
723             * _more_
724             * 
725             * @return _more_
726             */
727            protected Rectangle2D calculateRectangle() {
728                    try {
729                            Rectangle2D.Double box = stormDisplayState.getStormTrackControl()
730                                            .getNavigatedDisplay().getVisadBox();
731                            if (!box.isEmpty()) {
732                                    // pad rectangle by 5%
733                                    double deltaWidth = (double) (.05 * box.width);
734                                    double deltaHeight = (double) (.05 * box.height);
735                                    double newX = box.x - deltaWidth;
736                                    double newY = box.y - deltaHeight;
737                                    box.setRect(newX, newY, box.width + (2.0 * deltaWidth),
738                                                    box.height + (2.0 * deltaHeight));
739                            }
740                            return box;
741                    } catch (Exception excp) {
742                            LogUtil.logException("calculating Rectangle ", excp);
743                            return new Rectangle2D.Double(0, 0, 0, 0);
744                    }
745            }
746    
747            /**
748             * _more_
749             * 
750             * @return _more_
751             * 
752             * @throws Exception
753             *             _more_
754             */
755            public StationModelDisplayable getLabelDisplay() throws Exception {
756                    if (labelDisplay == null) {
757                            StationModel sm = (way.isObservation() ? stormDisplayState
758                                            .getObsLayoutModel() : stormDisplayState
759                                            .getForecastLayoutModel());
760                            if (sm != null) {
761                                    labelDisplay = new StationModelDisplayable("dots");
762                                    labelDisplay.setRotateShapes(true);
763                                    labelDisplay.setUseTimesInAnimation(false);
764                                    addDisplayable(labelDisplay);
765                                    labelDisplay.setScale(stormDisplayState.getStormTrackControl()
766                                                    .getDisplayScale());
767                            }
768                    }
769                    return labelDisplay;
770            }
771    
772            /**
773             * _more_
774             * 
775             * @return _more_
776             * 
777             * 
778             * @throws RemoteException
779             *             _more_
780             * @throws VisADException
781             *             _more_
782             */
783            public StationModelDisplayable getObsPointDisplay() throws VisADException,
784                            RemoteException {
785                    if (obsPointDisplay == null) {
786                            obsPointDisplay = new StationModelDisplayable("dots");
787                            obsPointDisplay.setRotateShapes(true);
788                            obsPointDisplay.setUseTimesInAnimation(false);
789                            addDisplayable(obsPointDisplay);
790                            obsPointDisplay.setScale(stormDisplayState.getStormTrackControl()
791                                            .getDisplayScale());
792                    }
793                    return obsPointDisplay;
794            }
795    
796            /**
797             * _more_
798             * 
799             * @return _more_
800             * 
801             * @throws Exception
802             *             _more_
803             */
804            public void initTrackDisplay() throws Exception {
805    
806                    trackDisplay = new TrackDisplayable("track_"
807                                    + stormDisplayState.getStormInfo().getStormId()); // +
808                                                                                                                                            // stormDisplayState.getColorParam(this));
809                    if (way.isObservation()) {
810                            trackDisplay.setLineWidth(3);
811                    } else {
812                            trackDisplay.setLineWidth(2);
813                            trackDisplay.setUseTimesInAnimation(false);
814                    }
815                    // setTrackColor();
816                    int cnt = holder.displayableCount();
817    
818                    for (int i = 0; i < cnt; i++) {
819                            Displayable dp = holder.getDisplayable(i);
820                            if (dp.getClass().isInstance(trackDisplay)) {
821                                    TrackDisplayable dd = (TrackDisplayable) dp;
822                                    if (dd.toString().equalsIgnoreCase(trackDisplay.toString())) {
823                                            holder.removeDisplayable(dp);
824                                            cnt = cnt - 1;
825                                    }
826                            }
827                    }
828    
829                    addDisplayable(trackDisplay);
830    
831            }
832    
833            /**
834             * _more_
835             * 
836             * @return _more_
837             * 
838             * @throws Exception
839             *             _more_
840             */
841            public TrackDisplayable getTrackDisplay() throws Exception {
842                    if (trackDisplay == null) {
843                            initTrackDisplay();
844                    }
845                    return trackDisplay;
846            }
847    
848            /**
849             * _more_
850             * 
851             * @return _more_
852             * 
853             * @throws Exception
854             *             _more_
855             */
856            public CompositeDisplayable getConesHolder() throws Exception {
857                    if (conesHolder == null) {
858                            conesHolder = new CompositeDisplayable("cone_"
859                                            + stormDisplayState.getStormInfo().getStormId());
860                            conesHolder.setVisible(true);
861                            conesHolder.setUseTimesInAnimation(false);
862                            addDisplayable(conesHolder);
863                    }
864                    return conesHolder;
865            }
866    
867            /**
868             * _more_
869             * 
870             * @return _more_
871             * 
872             * @throws Exception
873             *             _more_
874             */
875            public TrackDisplayable getRingsDisplay() throws Exception {
876                    if (ringsDisplay == null) {
877                            ringsDisplay = new TrackDisplayable("ring_"
878                                            + stormDisplayState.getStormInfo().getStormId() + "_"
879                                            + getWay());
880                            ringsDisplay.setVisible(true);
881                            ringsDisplay.setUseTimesInAnimation(false);
882                            addDisplayable(ringsDisplay);
883                    }
884                    return ringsDisplay;
885            }
886    
887            /**
888             * _more_
889             * 
890             * @param param
891             *            _more_
892             * @param mode
893             *            _more_
894             * 
895             * @return _more_
896             * 
897             * @throws Exception
898             *             _more_
899             */
900            public TrackDisplayable makeConeDisplay(StormParam param, int mode)
901                            throws Exception {
902                    FieldImpl field = makeConeField(param, mode);
903                    if (field == null) {
904                            return null;
905                    }
906                    TrackDisplayable coneDisplay = new TrackDisplayable("cone_"
907                                    + stormDisplayState.getStormInfo().getStormId());
908                    coneDisplay.setUseTimesInAnimation(false);
909                    coneDisplay.setTrack(field);
910                    coneDisplay.setUseTimesInAnimation(false);
911                    return coneDisplay;
912            }
913    
914            /**
915             * _more_
916             * 
917             * @param param
918             *            _more_
919             * @param mode
920             *            _more_
921             * 
922             * @return _more_
923             * 
924             * @throws Exception
925             *             _more_
926             */
927            public TrackDisplayable makeRingDisplay(StormParam param, int mode)
928                            throws Exception {
929                    FieldImpl field = makeRingsField(param, mode);
930                    if (field == null) {
931                            return null;
932                    }
933                    TrackDisplayable ringDisplay = new TrackDisplayable("ring_"
934                                    + stormDisplayState.getStormInfo().getStormId());
935                    ringDisplay.setUseTimesInAnimation(false);
936                    ringDisplay.setTrack(field);
937                    ringDisplay.setUseTimesInAnimation(false);
938                    return ringDisplay;
939            }
940    
941            /**
942             * _more_
943             * 
944             * @return _more_
945             */
946            protected JComponent getColorSwatch() {
947                    if (colorSwatch == null) {
948                            colorSwatch = new GuiUtils.ColorSwatch(getColor(),
949                                            "Set track color") {
950                                    public void setBackground(Color newColor) {
951                                            super.setBackground(newColor);
952                                            WayDisplayState.this.color = newColor;
953                                            try {
954                                                    setTrackColor();
955                                                    setConeColor();
956                                                    setRingsColor();
957                                                    setLabelColor();
958                                            } catch (Exception exc) {
959                                                    LogUtil.logException("Setting color", exc);
960                                            }
961                                    }
962                            };
963                            colorSwatch.setMinimumSize(new Dimension(15, 15));
964                            colorSwatch.setPreferredSize(new Dimension(15, 15));
965                    }
966                    return colorSwatch;
967            }
968    
969            /**
970             * _more_
971             * 
972             * @return _more_
973             */
974            public float[][] getColorPalette() {
975                    ColorTable ct = stormDisplayState.getColorTable(colorParam);
976                    if (ct != null) {
977                            return stormDisplayState.getStormTrackControl()
978                                            .getColorTableForDisplayable(ct);
979                    }
980                    return getColorPalette(getColor());
981            }
982    
983            /**
984             * _more_
985             * 
986             * @param c
987             *            _more_
988             * 
989             * @return _more_
990             */
991            public static float[][] getColorPalette(Color c) {
992                    if (c == null) {
993                            c = Color.red;
994                    }
995                    return ColorTableDefaults.allOneColor(c, true);
996            }
997    
998            /**
999             * _more_
1000             * 
1001             * @throws Exception
1002             *             _more_
1003             */
1004            private void setTrackColor() throws Exception {
1005                    if (trackDisplay != null) {
1006                            if (colorParam == null
1007                                            || colorParam.getName().equalsIgnoreCase("Fixed")) {
1008                                    trackDisplay.setColor(getColor());
1009                            } else
1010                                    trackDisplay.setColorPalette(getColorPalette());
1011                    }
1012    
1013            }
1014    
1015            /**
1016             * _more_
1017             * 
1018             * @throws Exception
1019             *             _more_
1020             */
1021            private void setLabelColor() throws Exception {
1022                    Color c = getColor();
1023                    if (labelDisplay != null) { // && !Misc.equals(c,
1024                                                                            // labelDisplay.getColor())) {
1025                            labelDisplay.setColor(c);
1026                    }
1027                    if (obsPointDisplay != null) { // && !Misc.equals(c,
1028                                                                                    // obsPointDisplay.getColor())) {
1029                            obsPointDisplay.setColor(c);
1030                    }
1031            }
1032    
1033            /**
1034             * _more_
1035             * 
1036             * @throws Exception
1037             *             _more_
1038             */
1039            private void setConeColor() throws Exception {
1040                    if (conesHolder != null) {
1041                            conesHolder.setColorPalette(getColorPalette(getColor()));
1042                    }
1043            }
1044    
1045            /**
1046             * _more_
1047             * 
1048             * @throws Exception
1049             *             _more_
1050             */
1051            private void setRingsColor() throws Exception {
1052                    if (ringsDisplay != null) {
1053                            ringsDisplay.setColor(getColor());
1054                    }
1055            }
1056    
1057            /**
1058             * _more_
1059             * 
1060             * @throws RemoteException
1061             *             _more_
1062             * @throws VisADException
1063             *             _more_
1064             */
1065            public void deactivate() throws VisADException, RemoteException {
1066                    ringsDisplay = null;
1067                    conesHolder = null;
1068                    if (holder != null) {
1069                    }
1070                    trackDisplay = null;
1071                    labelDisplay = null;
1072                    obsPointDisplay = null;
1073                    holder = null;
1074                    tracks = new ArrayList<StormTrack>();
1075                    times = new ArrayList<DateTime>();
1076            }
1077    
1078            /** _more_ */
1079            private static TextType fhourType;
1080    
1081            /** _more_ */
1082            private static TextType rhourType;
1083    
1084            /** _more_ */
1085            private static TextType shourType;
1086    
1087            /**
1088             * _more_
1089             * 
1090             * @param track
1091             *            _more_
1092             * 
1093             * @throws Exception
1094             *             _more_
1095             */
1096            public void addTrack(StormTrack track) throws Exception {
1097                    tracks.add(track);
1098            }
1099    
1100            /**
1101             * _more_
1102             * 
1103             * 
1104             * 
1105             * @param mode
1106             *            _more_
1107             * @return _more_
1108             * @throws Exception
1109             *             _more_
1110             */
1111    
1112            protected FieldImpl makeTrackField(int mode) throws Exception {
1113                    List<FieldImpl> fields = new ArrayList<FieldImpl>();
1114                    List<DateTime> times = new ArrayList<DateTime>();
1115    
1116                    // Use a local list to hold the point obs so we don't run into a race
1117                    // condition
1118                    List<PointOb> localPointObs = new ArrayList<PointOb>();
1119                    List<PointOb> localAllPointObs = new ArrayList<PointOb>();
1120                    Data[] datas = new Data[tracks.size()];
1121                    int i = 0;
1122                    for (StormTrack track : tracks) {
1123                            FieldImpl field = stormDisplayState.getStormTrackControl()
1124                                            .makeTrackField(track, colorParam);
1125                            if (field == null) {
1126                                    continue;
1127                            }
1128                            if (i == 0)
1129                                    datas[i++] = field;
1130                            else
1131                                    datas[i++] = field.changeMathType(datas[0].getType());
1132                            fields.add(field);
1133                            times.add(track.getStartTime());
1134                            // if(!way.isObservation() && mode == 0)
1135                            localPointObs.addAll(makePointObs(track, !way.isObservation()));
1136                            if (way.isObservation()) {
1137                                    localAllPointObs.addAll(makeObsPointObs(track));
1138                            }
1139                    }
1140    
1141                    pointObs = localPointObs;
1142                    allPointObs = localAllPointObs;
1143    
1144                    if (fields.size() == 0) {
1145                            return null;
1146                    }
1147                    // if(fields.size()==1) return fields.get(0);
1148    
1149                    if (!way.isObservation() && (mode == 1)) {
1150                            return Util.indexedField(datas, false);
1151                    } else {
1152                            return Util.makeTimeField(fields, times);
1153                    }
1154            }
1155    
1156            /**
1157             * _more_
1158             * 
1159             * 
1160             * @param stormParam
1161             *            _more_
1162             * @param mode
1163             *            _more_
1164             * 
1165             * @return _more_
1166             * @throws Exception
1167             *             _more_
1168             */
1169            protected FieldImpl makeConeField(StormParam stormParam, int mode)
1170                            throws Exception {
1171                    List<FieldImpl> fields = new ArrayList<FieldImpl>();
1172                    List<DateTime> times = new ArrayList<DateTime>();
1173                    Data[] datas = new Data[tracks.size()];
1174                    int i = 0;
1175                    for (StormTrack track : tracks) {
1176                            StormTrack coneTrack = makeConeTrack(track, stormParam);
1177                            if (coneTrack == null) {
1178                                    continue;
1179                            }
1180                            FieldImpl field = stormDisplayState.getStormTrackControl()
1181                                            .makeTrackField(coneTrack, null);
1182                            fields.add(field);
1183    
1184                            times.add(track.getStartTime());
1185                            datas[i++] = field;
1186                    }
1187    
1188                    if (fields.size() == 0) {
1189                            return null;
1190                    }
1191    
1192                    if (!way.isObservation() && (mode == 1)) {
1193                            return Util.indexedField(datas, false);
1194                    } else {
1195                            return Util.makeTimeField(fields, times);
1196                    }
1197            }
1198    
1199            /**
1200             * _more_
1201             * 
1202             * @param track
1203             *            _more_
1204             * @param useStartTime
1205             *            _more_
1206             * 
1207             * 
1208             * @return _more_
1209             * @throws Exception
1210             *             _more_
1211             */
1212            private List<PointOb> makePointObs(StormTrack track, boolean useStartTime)
1213                            throws Exception {
1214                    boolean isObservation = way.isObservation();
1215                    DateTime startTime = track.getStartTime();
1216                    List<StormTrackPoint> stps = track.getTrackPoints();
1217                    if (fhourType == null) {
1218                            fhourType = new TextType("fhour");
1219                    }
1220    
1221                    if (rhourType == null) {
1222                            rhourType = new TextType("rhour");
1223                    }
1224    
1225                    if (shourType == null) {
1226                            shourType = new TextType("shour");
1227                    }
1228                    List<PointOb> pointObs = new ArrayList<PointOb>();
1229    
1230                    DecimalFormat format = new DecimalFormat("0.#");
1231                    Date startDate = Util.makeDate(startTime);
1232                    List<StormParam> params = track.getParams();
1233                    for (int i = 0; i < stps.size(); i++) {
1234                            StormTrackPoint stp = stps.get(i);
1235                            DateTime time = (useStartTime ? startTime : stp.getTime());
1236                            String flabel = "";
1237                            String rlabel = "";
1238                            String slabel = "";
1239                            if (!isObservation) {
1240                                    if (i == 0) {
1241                                            // label = way.getId() + ": " + track.getStartTime();
1242                                    } else {
1243                                            flabel = "" + stp.getForecastHour() + "H";
1244                                            Date dttm = Util.makeDate(stp.getTime());
1245                                            rlabel = "" + dttm.toString();
1246                                            slabel = "" + getMonDayHour(dttm);
1247                                            ;
1248                                    }
1249                            } else if (useStartTime && (i > 0)) {
1250                                    Date dttm = Util.makeDate(stp.getTime());
1251                                    double diffSeconds = (dttm.getTime() - startDate.getTime()) / 1000.0;
1252                                    double diffHours = diffSeconds / 3600.0;
1253    
1254                                    flabel = format.format(diffHours) + "H";
1255                                    rlabel = "" + dttm.toString();
1256                                    slabel = "" + getMonDayHour(dttm);
1257                            }
1258                            Data[] data = new Data[params.size() + 3];
1259    
1260                            data[0] = new visad.Text(rhourType, rlabel);
1261                            data[1] = new visad.Text(fhourType, flabel);
1262                            data[2] = new visad.Text(shourType, slabel);
1263                            for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) {
1264                                    Real r = stp.getAttribute(params.get(paramIdx));
1265                                    if (r == null) {
1266                                            r = params.get(paramIdx).getReal(Double.NaN);
1267                                    }
1268                                    data[paramIdx + 3] = r;
1269    
1270                            }
1271                            Tuple tuple = new Tuple(data);
1272                            pointObs.add(PointObFactory.makePointOb(stp.getLocation(), time,
1273                                            tuple));
1274    
1275                    }
1276                    return pointObs;
1277            }
1278    
1279            /**
1280             * _more_
1281             * 
1282             * @param dt
1283             *            _more_
1284             * 
1285             * @return _more_
1286             */
1287            private String getMonDayHour(Date dt) {
1288                    Calendar cal = Calendar.getInstance();
1289    
1290                    cal.setTime(dt);
1291                    int m = cal.get(Calendar.MONTH) + 1; // 0 base, ie 0 for Jan
1292                    int d = cal.get(Calendar.DAY_OF_MONTH);
1293                    int h = cal.get(Calendar.HOUR_OF_DAY);
1294    
1295                    return "" + m + "/" + d + "/" + h + "H";
1296            }
1297    
1298            /**
1299             * _more_
1300             * 
1301             * @param track
1302             *            _more_
1303             * 
1304             * @return _more_
1305             * 
1306             * @throws Exception
1307             *             _more_
1308             */
1309            private List<PointOb> makeObsPointObs(StormTrack track) throws Exception {
1310                    DateTime startTime = track.getStartTime();
1311                    List<StormTrackPoint> stps = track.getTrackPoints();
1312                    if (fhourType == null) {
1313                            fhourType = new TextType("fhour");
1314                    }
1315                    if (rhourType == null) {
1316                            rhourType = new TextType("rhour");
1317                    }
1318                    if (shourType == null) {
1319                            shourType = new TextType("shour");
1320                    }
1321                    List<PointOb> pointObs = new ArrayList<PointOb>();
1322                    DecimalFormat format = new DecimalFormat("0.#");
1323                    List<StormParam> params = track.getParams();
1324                    for (int i = 0; i < stps.size(); i++) {
1325                            StormTrackPoint baseStp = stps.get(i);
1326                            DateTime baseTime = baseStp.getTime();
1327                            Date baseDate = Util.makeDate(baseTime);
1328                            for (int j = i; j < stps.size(); j++) {
1329                                    StormTrackPoint stp = stps.get(j);
1330                                    String flabel = "";
1331                                    String rlabel = "";
1332                                    String slabel = "";
1333                                    if (j > 0) {
1334                                            Date dttm = Util.makeDate(stp.getTime());
1335                                            double diffSeconds = (dttm.getTime() - baseDate.getTime()) / 1000.0;
1336                                            double diffHours = diffSeconds / 3600.0;
1337                                            flabel = format.format(diffHours) + "H";
1338                                            rlabel = "" + dttm.toString();
1339                                            slabel = "" + getMonDayHour(dttm);
1340                                    }
1341                                    Data[] data = new Data[params.size() + 3];
1342                                    data[0] = new visad.Text(fhourType, flabel);
1343                                    data[1] = new visad.Text(rhourType, rlabel);
1344                                    data[2] = new visad.Text(shourType, slabel);
1345                                    for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) {
1346                                            Real r = stp.getAttribute(params.get(paramIdx));
1347                                            if (r == null) {
1348                                                    r = params.get(paramIdx).getReal(Double.NaN);
1349                                            }
1350                                            data[paramIdx + 3] = r;
1351                                    }
1352                                    Tuple tuple = new Tuple(data);
1353                                    pointObs.add(PointObFactory.makePointOb(stp.getLocation(),
1354                                                    baseTime, tuple));
1355                            }
1356                    }
1357                    return pointObs;
1358            }
1359    
1360            /**
1361             * _more_
1362             * 
1363             * @return _more_
1364             */
1365            public List<StormTrack> getTracks() {
1366                    return tracks;
1367            }
1368    
1369            /**
1370             * _more_
1371             * 
1372             * @return _more_
1373             */
1374            public List<DateTime> getTimes() {
1375                    return times;
1376            }
1377    
1378            /**
1379             * _more_
1380             * 
1381             * @param displayable
1382             *            _more_
1383             * 
1384             * 
1385             * @throws RemoteException
1386             *             _more_
1387             * @throws VisADException
1388             *             _more_
1389             */
1390            public void addDisplayable(Displayable displayable) throws VisADException,
1391                            RemoteException {
1392                    getHolder().addDisplayable(displayable);
1393            }
1394    
1395            /**
1396             * _more_
1397             * 
1398             * @param displayable
1399             *            _more_
1400             * 
1401             * @throws RemoteException
1402             *             _more_
1403             * @throws VisADException
1404             *             _more_
1405             */
1406            public void removeDisplayable(Displayable displayable)
1407                            throws VisADException, RemoteException {
1408                    getHolder().removeDisplayable(displayable);
1409            }
1410    
1411            /**
1412             * Set the ConeState property.
1413             * 
1414             * @param value
1415             *            The new value for ConeState
1416             */
1417            public void setConeState(DisplayState value) {
1418                    coneState = value;
1419            }
1420    
1421            /**
1422             * Get the ConeState property.
1423             * 
1424             * @return The ConeState
1425             */
1426            public DisplayState getConeState() {
1427                    return coneState;
1428            }
1429    
1430            /**
1431             * Set the TrackState property.
1432             * 
1433             * @param value
1434             *            The new value for TrackState
1435             */
1436            public void setTrackState(DisplayState value) {
1437                    trackState = value;
1438            }
1439    
1440            /**
1441             * Get the TrackState property.
1442             * 
1443             * @return The TrackState
1444             */
1445            public DisplayState getTrackState() {
1446                    return trackState;
1447            }
1448    
1449            /**
1450             * Set the RingsState property.
1451             * 
1452             * @param value
1453             *            The new value for RingsState
1454             */
1455            public void setRingsState(DisplayState value) {
1456                    ringsState = value;
1457            }
1458    
1459            /**
1460             * Get the RingsState property.
1461             * 
1462             * @return The RingsState
1463             */
1464            public DisplayState getRingsState() {
1465                    return ringsState;
1466            }
1467    
1468            /**
1469             * Set the WayState property.
1470             * 
1471             * @param value
1472             *            The new value for WayState
1473             */
1474            public void setWayState(DisplayState value) {
1475                    wayState = value;
1476            }
1477    
1478            /**
1479             * Get the WayState property.
1480             * 
1481             * @return The WayState
1482             */
1483            public DisplayState getWayState() {
1484                    return wayState;
1485            }
1486    
1487            /**
1488             * Set the Color property.
1489             * 
1490             * @param value
1491             *            The new value for Color
1492             */
1493            public void setColor(Color value) {
1494                    color = value;
1495            }
1496    
1497            /**
1498             * Get the Color property.
1499             * 
1500             * @return The Color
1501             */
1502            public Color getColor() {
1503                    return color;
1504            }
1505    
1506            /**
1507             * Set the StormDisplayState property.
1508             * 
1509             * @param value
1510             *            The new value for StormDisplayState
1511             */
1512            public void setStormDisplayState(StormDisplayState value) {
1513                    stormDisplayState = value;
1514            }
1515    
1516            /**
1517             * Get the StormDisplayState property.
1518             * 
1519             * @return The StormDisplayState
1520             */
1521            public StormDisplayState getStormDisplayState() {
1522                    return stormDisplayState;
1523            }
1524    
1525            /**
1526             * Set the Way property.
1527             * 
1528             * @param value
1529             *            The new value for Way
1530             */
1531            public void setWay(Way value) {
1532                    way = value;
1533            }
1534    
1535            /**
1536             * Get the Way property.
1537             * 
1538             * @return The Way
1539             */
1540            public Way getWay() {
1541                    return way;
1542            }
1543    
1544            /**
1545             * Set the ColorTable property.
1546             * 
1547             * @param value
1548             *            The new value for ColorTable
1549             */
1550            public void setColorTable(String value) {
1551            }
1552    
1553            /**
1554             * _more_
1555             * 
1556             * @param track
1557             *            _more_
1558             * @param param
1559             *            _more_
1560             * 
1561             * @return _more_
1562             */
1563            private List<StormTrackPoint> getRealTrackPoints(StormTrack track,
1564                            StormParam param) {
1565                    List<StormTrackPoint> newStps = new ArrayList();
1566                    List<StormTrackPoint> stps = track.getTrackPoints();
1567    
1568                    newStps.add(stps.get(0));
1569                    Iterator<StormTrackPoint> it = stps.iterator();
1570    
1571                    while (it.hasNext()) {
1572                            StormTrackPoint stp = it.next();
1573                            if (stp.getAttribute(param) != null) {
1574                                    newStps.add(stp);
1575                            }
1576                    }
1577                    return newStps;
1578            }
1579    
1580            /**
1581             * _more_
1582             * 
1583             * @param stormParam
1584             *            _more_
1585             * @param mode
1586             *            _more_
1587             * 
1588             * @return _more_
1589             * 
1590             * @throws Exception
1591             *             _more_
1592             */
1593            protected FieldImpl makeRingsField(StormParam stormParam, int mode)
1594                            throws Exception {
1595                    List<FieldImpl> fields = new ArrayList<FieldImpl>();
1596                    List<DateTime> times = new ArrayList<DateTime>();
1597                    Data[] datas = new Data[tracks.size() * 10];
1598                    int i = 0;
1599    
1600                    if (!way.isObservation() && (mode == 1)) {
1601                            for (StormTrack track : tracks) {
1602                                    List<StormTrack> stList = makeRingTrackList(track, stormParam);
1603                                    for (StormTrack stk : stList) {
1604                                            FieldImpl field = stormDisplayState.getStormTrackControl()
1605                                                            .makeTrackField(stk, null);
1606                                            fields.add(field);
1607                                            datas[i++] = field;
1608                                    }
1609    
1610                            }
1611    
1612                            return Util.indexedField(datas, false);
1613                    }
1614    
1615                    for (StormTrack track : tracks) {
1616                            FieldImpl ringField = makeRingTracks(track, stormParam);
1617                            fields.add(ringField);
1618                            times.add(track.getStartTime());
1619                    }
1620    
1621                    if (fields.size() == 0) {
1622                            return null;
1623                    }
1624    
1625                    return Util.makeTimeField(fields, times);
1626            }
1627    
1628            /**
1629             * _more_
1630             * 
1631             * @param track
1632             *            _more_
1633             * @param param
1634             *            _more_
1635             * 
1636             * @return _more_
1637             * 
1638             * @throws Exception
1639             *             _more_
1640             */
1641            public List makeRingTrackList(StormTrack track, StormParam param)
1642                            throws Exception {
1643                    List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1644                    List<StormTrack> stracks = new ArrayList();
1645    
1646                    int size = stps.size();
1647                    DateTime dt = stps.get(0).getTime();
1648                    Way ringWay = new Way(getWay() + "_RING");
1649                    int numberOfPoints = 73;
1650                    double angleDelta = 360.0 / (numberOfPoints - 1);
1651                    for (int i = 0; i < size; i++) {
1652                            StormTrackPoint stp = stps.get(i);
1653                            Real r = stp.getAttribute(param);
1654                            if (r != null) {
1655                                    double rr = r.getValue();
1656                                    double azi = 0.0;
1657                                    List ringList = new ArrayList<StormTrackPoint>();
1658                                    for (int j = 0; j < numberOfPoints; j++) {
1659                                            ringList.add(getCirclePoint(stp, rr, azi, dt));
1660                                            azi = azi + angleDelta;
1661                                    }
1662                                    stracks.add(new StormTrack(track.getStormInfo(), ringWay,
1663                                                    ringList, null));
1664                            }
1665                    }
1666    
1667                    return stracks;
1668    
1669            }
1670    
1671            /**
1672             * _more_
1673             * 
1674             * @param track
1675             *            _more_
1676             * @param param
1677             *            _more_
1678             * 
1679             * @return _more_
1680             * 
1681             * 
1682             * @throws Exception
1683             *             _more_
1684             */
1685            public FieldImpl makeRingTracks(StormTrack track, StormParam param)
1686                            throws Exception {
1687                    List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1688                    List<StormTrack> stracks = new ArrayList();
1689                    int size = stps.size();
1690                    DateTime dt = stps.get(0).getTime();
1691                    Way ringWay = new Way(getWay() + "_RING");
1692                    int numberOfPoints = 73;
1693                    double angleDelta = 360.0 / (numberOfPoints - 1);
1694                    for (int i = 0; i < size; i++) {
1695                            StormTrackPoint stp = stps.get(i);
1696                            Real r = stp.getAttribute(param);
1697                            if (r != null) {
1698                                    double rr = r.getValue();
1699                                    double azi = 0.0;
1700                                    List ringList = new ArrayList<StormTrackPoint>();
1701                                    for (int j = 0; j < numberOfPoints; j++) {
1702                                            ringList.add(getCirclePoint(stp, rr, azi, dt));
1703                                            azi = azi + angleDelta;
1704                                    }
1705                                    stracks.add(new StormTrack(track.getStormInfo(), ringWay,
1706                                                    ringList, null));
1707                            }
1708                    }
1709    
1710                    Data[] datas = new Data[stracks.size()];
1711                    int i = 0;
1712                    for (StormTrack ringTrack : stracks) {
1713                            datas[i++] = stormDisplayState.getStormTrackControl()
1714                                            .makeTrackField(ringTrack, null);
1715                    }
1716                    return Util.indexedField(datas, false);
1717            }
1718    
1719            /**
1720             * _more_
1721             * 
1722             * @param stp
1723             *            _more_
1724             * @param r0
1725             *            _more_
1726             * @param azimuth
1727             *            _more_
1728             * @param dt
1729             *            _more_
1730             * 
1731             * @return _more_
1732             * 
1733             * @throws VisADException
1734             *             _more_
1735             */
1736            public StormTrackPoint getCirclePoint(StormTrackPoint stp, double r0,
1737                            double azimuth, DateTime dt) throws VisADException {
1738                    //
1739    
1740                    EarthLocation el = stp.getLocation();
1741                    double lat0 = el.getLatitude().getValue();
1742                    double lon0 = el.getLongitude().getValue();
1743                    // DateTime dt = stp.getTime();
1744                    LatLonPointImpl lp = Bearing.findPoint(lat0, lon0, azimuth, r0, null);
1745    
1746                    EarthLocation el1 = new EarthLocationLite(lp.getLatitude(), lp
1747                                    .getLongitude(), 0);
1748                    StormTrackPoint stp1 = new StormTrackPoint(el1, dt, 0, null);
1749    
1750                    return stp1;
1751            }
1752    
1753            /**
1754             * old
1755             * 
1756             * @param track
1757             *            _more_
1758             * @param param
1759             *            _more_
1760             * 
1761             * @return _more_
1762             * 
1763             * @throws VisADException
1764             *             _more_
1765             */
1766            public StormTrack makeConeTrack_Old(StormTrack track, StormParam param)
1767                            throws VisADException {
1768                    List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1769                    int size = stps.size();
1770                    int numberOfPoint = size * 2 + 11;
1771                    StormTrackPoint[] conePoints = new StormTrackPoint[numberOfPoint];
1772    
1773                    StormTrackPoint stp1 = stps.get(0);
1774                    conePoints[0] = stp1; // first point & last point
1775                    conePoints[numberOfPoint - 1] = stp1;
1776    
1777                    StormTrackPoint stp2;
1778                    StormTrackPoint stp;
1779    
1780                    // circle 1 to n
1781    
1782                    for (int i = 1; i < size; i++) {
1783                            stp2 = stps.get(i);
1784                            // right point
1785                            stp = getPointToCircleTangencyPoint(stp1, stp2, param, true);
1786                            conePoints[i] = stp;
1787                            // left point
1788                            stp = getPointToCircleTangencyPoint(stp1, stp2, param, false);
1789                            conePoints[numberOfPoint - i - 1] = stp;
1790                            stp1 = stp2;
1791                    }
1792    
1793                    // end point half circle take 11 points
1794                    StormTrackPoint last = stps.get(size - 1);
1795                    EarthLocation lastEl = last.getLocation();
1796                    StormTrackPoint endSTP = conePoints[size - 1];
1797                    int ii = 0;
1798                    while ((endSTP == null) && (ii < (size - 2))) {
1799                            ii++;
1800                            last = stps.get(size - 1 - ii);
1801                            lastEl = last.getLocation();
1802                            endSTP = conePoints[size - 1 - ii];
1803                    }
1804    
1805                    if ((endSTP == null) || (ii == (size - 2))) {
1806                            return null;
1807                    }
1808                    EarthLocation endEl = endSTP.getLocation();
1809    
1810                    double ang = getCircleAngleRange(lastEl, endEl);
1811    
1812                    Real r = last.getAttribute(param);
1813                    StormTrackPoint[] halfCircle = getHalfCircleTrackPoint(lastEl, ang,
1814                                    ((r != null) ? r.getValue() : 0), last.getTime());
1815    
1816                    for (int i = 0; i < 11; i++) {
1817                            conePoints[size + i] = halfCircle[i];
1818                    }
1819    
1820                    List coneList = new ArrayList<StormTrackPoint>();
1821                    for (int i = 0; i < numberOfPoint; i++) {
1822                            if (conePoints[i] != null) {
1823                                    coneList.add(conePoints[i]);
1824                            }
1825                    }
1826    
1827                    return new StormTrack(track.getStormInfo(),
1828                                    new Way(getWay() + "_CONE"), coneList, null);
1829    
1830            }
1831    
1832            /**
1833             * construct the cone track as track of point to circle and circle to circle
1834             * 
1835             * @param track
1836             *            _more_
1837             * @param param
1838             *            _more_
1839             * 
1840             * @return _more_
1841             * 
1842             * @throws VisADException
1843             *             _more_
1844             */
1845            public StormTrack makeConeTrack(StormTrack track, StormParam param)
1846                            throws VisADException {
1847    
1848                    List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1849                    int size = stps.size();
1850                    int numberOfPoint = size * 2 + 100;
1851                    List<StormTrackPoint> conePointsLeft = new ArrayList<StormTrackPoint>();
1852                    List<StormTrackPoint> conePointsRight = new ArrayList<StormTrackPoint>();
1853    
1854                    StormTrackPoint stp1 = stps.get(0);
1855                    conePointsRight.add(stp1); // first point & last point
1856                    conePointsLeft.add(stp1);
1857                    StormTrackPoint stp2 = stps.get(1);
1858                    StormTrackPoint stp3 = stps.get(2);
1859                    int nn = 3;
1860                    // first point to circle
1861                    List<StormTrackPoint> p2c = getPointToCircleTangencyPointA(stp1, stp2,
1862                                    stp3, param, true);
1863                    while (p2c == null) { // need to find the first point with param value
1864                            stp2 = stp3;
1865                            if (nn < size) {
1866                                    stp3 = stps.get(nn);
1867                            } else {
1868                                    stp3 = null;
1869                                    // return null;
1870                            }
1871                            p2c = getPointToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1872                            nn++;
1873                            if (nn >= size) {
1874                                    break;
1875                            }
1876                    }
1877                    if (p2c != null) {
1878                            conePointsRight.addAll(p2c);
1879                            p2c = getPointToCircleTangencyPointA(stp1, stp2, stp3, param, false);
1880                            conePointsLeft.addAll(p2c);
1881                    }
1882    
1883                    // circle to circle 1 to n
1884                    stp1 = stp2;
1885                    stp2 = stp3;
1886                    for (int i = nn; i < size; i++) {
1887                            stp3 = stps.get(i);
1888                            // right point
1889                            p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1890                            if (p2c != null) {
1891                                    conePointsRight.addAll(p2c);
1892                                    // left point
1893                                    p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param,
1894                                                    false);
1895                                    conePointsLeft.addAll(p2c);
1896                                    stp1 = stp2; // update the first point only after the valid
1897                                                                    // second point
1898                            }
1899    
1900                            stp2 = stp3;
1901                    }
1902                    // last circle
1903                    stp3 = null;
1904                    p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1905                    if (p2c != null) {
1906                            conePointsRight.addAll(p2c);
1907                            // left point
1908                            p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param,
1909                                            false);
1910                            conePointsLeft.addAll(p2c);
1911                            stp1 = stp2;
1912                    }
1913    
1914                    // end point half circle take 11 points
1915                    StormTrackPoint last = stp2;
1916                    if (last == null) {
1917                            last = stp1;
1918                    }
1919                    if (last == null) {
1920                            return null;
1921                    }
1922                    EarthLocation lastEl = last.getLocation();
1923                    StormTrackPoint endSTP = conePointsRight
1924                                    .get(conePointsRight.size() - 1);
1925                    /*
1926                     * int ii = 0; while ((endSTP == null) && (ii < (size - 2))) { ii++;
1927                     * last = stps.get(size - 1 - ii); lastEl = last.getLocation(); endSTP =
1928                     * conePointsRight.get(size - 1 - ii); }
1929                     * 
1930                     * if ((endSTP == null) || (ii == (size - 2))) { return null; }
1931                     */
1932                    if (endSTP == null) {
1933                            return null;
1934                    }
1935                    EarthLocation endEl = endSTP.getLocation();
1936    
1937                    double ang = getCircleAngleRange(lastEl, endEl);
1938    
1939                    Real r = last.getAttribute(param);
1940                    StormTrackPoint[] halfCircle = getHalfCircleTrackPoint(lastEl, ang,
1941                                    ((r != null) ? r.getValue() : 0), last.getTime());
1942    
1943                    for (int i = 0; i < 11; i++) {
1944                    }
1945                    // merge three lists
1946                    List<StormTrackPoint> coneList = new ArrayList<StormTrackPoint>();
1947                    int s1 = conePointsRight.size();
1948                    for (int i = 0; i < s1; i++) {
1949                            if (conePointsRight.get(i) != null) {
1950                                    coneList.add(conePointsRight.get(i));
1951                            }
1952                    }
1953                    for (int i = 0; i < 11; i++) {
1954                            coneList.add(halfCircle[i]);
1955                    }
1956                    int s2 = conePointsLeft.size();
1957                    for (int i = s2; i > 0; i--) {
1958                            if (conePointsLeft.get(i - 1) != null) {
1959                                    coneList.add(conePointsLeft.get(i - 1));
1960                            }
1961                    }
1962    
1963                    return new StormTrack(track.getStormInfo(),
1964                                    new Way(getWay() + "_CONE"), coneList, null);
1965    
1966            }
1967    
1968            /**
1969             * calculate the bearing of two storm track points
1970             * 
1971             * @param sp1
1972             *            _more_
1973             * @param sp2
1974             *            _more_
1975             * 
1976             * @return _more_
1977             */
1978            public Bearing getStormPoinsBearing(StormTrackPoint sp1, StormTrackPoint sp2) {
1979                    EarthLocation el1 = sp1.getLocation();
1980                    EarthLocation el2 = sp2.getLocation();
1981                    return Bearing.calculateBearing(el1.getLatitude().getValue(), el1
1982                                    .getLongitude().getValue(), el2.getLatitude().getValue(), el2
1983                                    .getLongitude().getValue(), null);
1984    
1985            }
1986    
1987            /**
1988             * get the tangency point to the circle of the second point and the third
1989             * point as its direction of adding additional points
1990             * 
1991             * @param sp1
1992             *            outside point
1993             * @param sp2
1994             *            the center of the circle
1995             * @param sp3
1996             *            _more_
1997             * @param param
1998             *            _more_
1999             * @param right
2000             *            _more_
2001             * 
2002             * @return _more_
2003             * 
2004             * @throws VisADException
2005             *             _more_
2006             */
2007            public List<StormTrackPoint> getPointToCircleTangencyPointA(
2008                            StormTrackPoint sp1, StormTrackPoint sp2, StormTrackPoint sp3,
2009                            StormParam param, boolean right) throws VisADException {
2010    
2011                    List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2012                    if (sp3 == null) {
2013                            return getPointToCircleTangencyPointB(sp1, sp2, param, right);
2014                    }
2015    
2016                    EarthLocation el1 = sp1.getLocation();
2017                    EarthLocation el2 = sp2.getLocation();
2018                    EarthLocation el3 = sp3.getLocation();
2019    
2020                    Real rl = sp2.getAttribute(param);
2021                    double r = rl.getValue();
2022    
2023                    if (Float.isNaN((float) r) || (r == 0.0)) {
2024                            return null;
2025                    }
2026    
2027                    double lat1 = el1.getLatitude().getValue();
2028                    double lon1 = el1.getLongitude().getValue();
2029    
2030                    double lat2 = el2.getLatitude().getValue();
2031                    double lon2 = el2.getLongitude().getValue();
2032    
2033                    double lat3 = el3.getLatitude().getValue();
2034                    double lon3 = el3.getLongitude().getValue();
2035    
2036                    Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2037                    Bearing c = Bearing.calculateBearing(lat2, lon2, lat3, lon3, null);
2038                    double dist1 = b.getDistance();
2039    
2040                    if (dist1 < r) { // first point is inside the circle
2041                            trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2042                                            right));
2043                            return trackPoints;
2044                    }
2045    
2046                    double af = getCircleAngleRange(el1, el2);
2047                    double ddt = Math.abs(b.getAngle() - c.getAngle());
2048                    double bt = getCircleTangencyAngle(el1, el2, r);
2049    
2050                    af = af * 180.0 / Math.PI;
2051                    bt = bt * 180.0 / Math.PI;
2052                    if (right) {
2053                            af = af - 90;
2054                    } else {
2055                            af = af + 90;
2056                    }
2057                    // change angle to azimuth
2058                    double az = af;
2059                    if ((af <= 90) && (af >= 0)) {
2060                            az = 90 - af;
2061                    } else if ((af > 90) && (af <= 180)) {
2062                            az = 360 + (90 - af);
2063                    } else if ((af < 0) && (af >= -180)) {
2064                            az = 90 - af;
2065                    } else if ((af > 180) && (af <= 360)) {
2066                            az = 450 - af;
2067                    } else if ((af < -180) && (af >= -360)) {
2068                            az = -270 - af;
2069                    }
2070                    if (right) {
2071                            az = az + bt;
2072                    } else {
2073                            az = az - bt;
2074                    }
2075    
2076                    if (ddt > 270) {
2077                            ddt = 360 - ddt;
2078                    } else if (ddt > 180) {
2079                            ddt = ddt - 180;
2080                    } else if (ddt > 90) {
2081                            ddt = ddt - 90;
2082                    }
2083    
2084                    double dt = bt;
2085    
2086                    if (right) {
2087                            if ((c.getAngle() < b.getAngle())
2088                                            && (Math.abs(b.getAngle() - c.getAngle()) < 90)) {
2089                                    dt = bt + ddt;
2090                            } else if ((c.getAngle() > b.getAngle())
2091                                            && (Math.abs(b.getAngle() - c.getAngle()) > 180)) {
2092                                    dt = bt + ddt;
2093                            } else {
2094                                    dt = bt - ddt;
2095                            }
2096                    } else {
2097                            if ((c.getAngle() > b.getAngle())
2098                                            && (Math.abs(b.getAngle() - c.getAngle()) < 90)) {
2099                                    dt = bt + ddt;
2100                            } else if ((c.getAngle() < b.getAngle())
2101                                            && (Math.abs(b.getAngle() - c.getAngle()) > 180)) {
2102                                    dt = bt + ddt;
2103                            } else {
2104                                    dt = bt - ddt;
2105                            }
2106    
2107                    }
2108    
2109                    int n = (int) dt / 5 + 1;
2110                    if (n <= 0) {
2111                            n = 1;
2112                    }
2113                    double dtt = dt / n;
2114                    if (dtt < 0) {
2115                            dtt = 0;
2116                            n = 1;
2117                    }
2118                    for (int i = 0; i < n; i++) {
2119    
2120                            LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2121                            // add more points along the circle
2122    
2123                            EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2124                                            .getLongitude(), 0);
2125                            trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2126                            if (right) {
2127                                    az = az - dtt;
2128                            } else {
2129                                    az = az + dtt;
2130                            }
2131                    }
2132    
2133                    return trackPoints;
2134            }
2135    
2136            /**
2137             * get the tangency point to the circle of the second point
2138             * 
2139             * @param sp1
2140             *            _more_
2141             * @param sp2
2142             *            _more_
2143             * @param param
2144             *            _more_
2145             * @param right
2146             *            _more_
2147             * 
2148             * @return _more_
2149             * 
2150             * @throws VisADException
2151             *             _more_
2152             */
2153            public List<StormTrackPoint> getPointToCircleTangencyPointB(
2154                            StormTrackPoint sp1, StormTrackPoint sp2, StormParam param,
2155                            boolean right) throws VisADException {
2156    
2157                    List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2158    
2159                    if (sp2 == null) {
2160                            return null;
2161                    }
2162                    EarthLocation el1 = sp1.getLocation();
2163                    EarthLocation el2 = sp2.getLocation();
2164    
2165                    Real rl = sp2.getAttribute(param);
2166                    double r = rl.getValue();
2167    
2168                    if (Float.isNaN((float) r) || (r == 0.0)) {
2169                            return null;
2170                    }
2171    
2172                    double lat1 = el1.getLatitude().getValue();
2173                    double lon1 = el1.getLongitude().getValue();
2174    
2175                    double lat2 = el2.getLatitude().getValue();
2176                    double lon2 = el2.getLongitude().getValue();
2177    
2178                    Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2179                    double dist1 = b.getDistance();
2180    
2181                    if (dist1 < r) { // first point is inside the circle
2182                            trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2183                                            right));
2184                            return trackPoints;
2185                    }
2186    
2187                    double af = getCircleAngleRange(el1, el2);
2188                    double bt = getCircleTangencyAngle(el1, el2, r);
2189    
2190                    af = af * 180.0 / Math.PI;
2191                    bt = bt * 180.0 / Math.PI;
2192                    if (right) {
2193                            af = af - 90;
2194                    } else {
2195                            af = af + 90;
2196                    }
2197                    // change angle to azimuth
2198                    double az = af;
2199                    if ((af <= 90) && (af >= 0)) {
2200                            az = 90 - af;
2201                    } else if ((af > 90) && (af <= 180)) {
2202                            az = 360 + (90 - af);
2203                    } else if ((af < 0) && (af >= -180)) {
2204                            az = 90 - af;
2205                    } else if ((af > 180) && (af <= 360)) {
2206                            az = 450 - af;
2207                    } else if ((af < -180) && (af >= -360)) {
2208                            az = -270 - af;
2209                    }
2210                    if (right) {
2211                            az = az + bt;
2212                    } else {
2213                            az = az - bt;
2214                    }
2215    
2216                    double dt = bt;
2217    
2218                    int n = (int) dt / 5 + 1;
2219                    double dtt = dt / n;
2220                    for (int i = 0; i < n; i++) {
2221    
2222                            LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2223                            // add more points along the circle
2224    
2225                            EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2226                                            .getLongitude(), 0);
2227                            trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2228                            if (right) {
2229                                    az = az - dtt;
2230                            } else {
2231                                    az = az + dtt;
2232                            }
2233                    }
2234    
2235                    return trackPoints;
2236            }
2237    
2238            /**
2239             * get the approximate tangency points of circle to the circle
2240             * 
2241             * @param sp1
2242             *            outside point
2243             * @param sp2
2244             *            the center of the circle
2245             * @param sp3
2246             *            _more_
2247             * @param param
2248             *            _more_
2249             * @param right
2250             *            _more_
2251             * 
2252             * @return _more_
2253             * 
2254             * @throws VisADException
2255             *             _more_
2256             */
2257            public List<StormTrackPoint> getCircleToCircleTangencyPointA(
2258                            StormTrackPoint sp1, StormTrackPoint sp2, StormTrackPoint sp3,
2259                            StormParam param, boolean right) throws VisADException {
2260    
2261                    List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2262                    if (sp3 == null) {
2263                            if (sp2 == null) {
2264                                    return null;
2265                            }
2266                            trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2267                                            right));
2268                            return trackPoints;
2269                    }
2270    
2271                    EarthLocation el1 = sp1.getLocation();
2272                    EarthLocation el2 = sp2.getLocation();
2273                    EarthLocation el3 = sp3.getLocation();
2274    
2275                    Real rl = sp2.getAttribute(param);
2276                    double r = rl.getValue();
2277    
2278                    if (Float.isNaN((float) r) || (r == 0.0)) {
2279                            return null;
2280                    }
2281    
2282                    double lat1 = el1.getLatitude().getValue();
2283                    double lon1 = el1.getLongitude().getValue();
2284    
2285                    double lat2 = el2.getLatitude().getValue();
2286                    double lon2 = el2.getLongitude().getValue();
2287    
2288                    double lat3 = el3.getLatitude().getValue();
2289                    double lon3 = el3.getLongitude().getValue();
2290    
2291                    Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2292                    double dist1 = b.getDistance();
2293                    Bearing c = Bearing.calculateBearing(lat2, lon2, lat3, lon3, null);
2294                    double x = Math.abs(c.getAngle() - b.getAngle());
2295    
2296                    if (right) {
2297                            if ((c.getAngle() > b.getAngle()) || (x > 180)) {
2298                                    trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2299                                                    right));
2300                                    return trackPoints;
2301                            }
2302                    }
2303    
2304                    if (!right) {
2305                            if ((c.getAngle() < b.getAngle()) && (x < 90)) {
2306                                    trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2307                                                    right));
2308                                    return trackPoints;
2309                            }
2310                    }
2311                    double af = getCircleAngleRange(el1, el2);
2312                    double dt = 0; // = Math.abs(b.getAngle() - c.getAngle());
2313    
2314                    if (x > 270) {
2315                            dt = 360 - x;
2316                    } else if (x > 180) {
2317                            dt = x - 180;
2318                    } else if (x > 90) {
2319                            dt = x - 90;
2320                    } else {
2321                            dt = x;
2322                    }
2323    
2324                    af = af * 180.0 / Math.PI;
2325                    if (right) {
2326                            af = af - 90;
2327                    } else {
2328                            af = af + 90;
2329                    }
2330                    // change angle to azimuth
2331                    double az = af;
2332                    if ((af <= 90) && (af >= 0)) {
2333                            az = 90 - af;
2334                    } else if ((af > 90) && (af <= 180)) {
2335                            az = 360 + (90 - af);
2336                    } else if ((af < 0) && (af >= -180)) {
2337                            az = 90 - af;
2338                    } else if ((af > 180) && (af <= 360)) {
2339                            az = 450 - af;
2340                    } else if ((af < -180) && (af >= -360)) {
2341                            az = -270 - af;
2342                    }
2343    
2344                    int n = (int) dt / 5 + 1;
2345                    double dtt = dt / n;
2346    
2347                    for (int i = 0; i < n; i++) {
2348    
2349                            LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2350                            // add more points along the circle
2351    
2352                            EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2353                                            .getLongitude(), 0);
2354                            trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2355                            if (right) {
2356                                    az = az - dtt;
2357                            } else {
2358                                    az = az + dtt;
2359                            }
2360                    }
2361    
2362                    return trackPoints;
2363            }
2364    
2365            /**
2366             * get the 90 degree point to the line of the two points
2367             * 
2368             * @param sp1
2369             *            _more_
2370             * @param sp2
2371             *            _more_
2372             * @param param
2373             *            _more_
2374             * @param right
2375             *            _more_
2376             * 
2377             * @return _more_
2378             * 
2379             * @throws VisADException
2380             *             _more_
2381             */
2382            public StormTrackPoint getPointToCircleTangencyPoint(StormTrackPoint sp1,
2383                            StormTrackPoint sp2, StormParam param, boolean right)
2384                            throws VisADException {
2385    
2386                    EarthLocation el1 = sp1.getLocation();
2387                    EarthLocation el2 = sp2.getLocation();
2388    
2389                    Real rl = sp2.getAttribute(param);
2390                    double r = rl.getValue();
2391    
2392                    if (Float.isNaN((float) r) || (r == 0.0)) {
2393                            return null;
2394                    }
2395    
2396                    double lat2 = el2.getLatitude().getValue();
2397                    double lon2 = el2.getLongitude().getValue();
2398    
2399                    double af = getCircleAngleRange(el1, el2);
2400                    af = af * 180.0 / Math.PI;
2401                    if (right) {
2402                            af = af - 90;
2403                    } else {
2404                            af = af + 90;
2405                    }
2406                    // change angle to azimuth
2407                    if ((af <= 90) && (af >= 0)) {
2408                            af = 90 - af;
2409                    } else if ((af > 90) && (af <= 180)) {
2410                            af = 360 + (90 - af);
2411                    } else if ((af < 0) && (af >= -180)) {
2412                            af = 90 - af;
2413                    } else if ((af > 180) && (af <= 360)) {
2414                            af = 450 - af;
2415                    } else if ((af < -180) && (af >= -360)) {
2416                            af = -270 - af;
2417                    }
2418    
2419                    LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, af, r, null);
2420    
2421                    EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2422                                    .getLongitude(), 0);
2423                    StormTrackPoint sp = new StormTrackPoint(el, sp1.getTime(), 0, null);
2424                    return sp;
2425            }
2426    
2427            /**
2428             * _more_
2429             * 
2430             * @param c
2431             *            _more_
2432             * @param d
2433             *            _more_
2434             * @param r
2435             *            _more_
2436             * 
2437             * @return _more_
2438             */
2439            public double getCircleTangencyAngle(EarthLocation c, EarthLocation d,
2440                            double r) {
2441    
2442                    double lat1 = c.getLatitude().getValue();
2443                    double lon1 = c.getLongitude().getValue();
2444    
2445                    double lat2 = d.getLatitude().getValue();
2446                    double lon2 = d.getLongitude().getValue();
2447    
2448                    Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2449                    double dist = b.getDistance();
2450                    double a = Math.asin(r / dist);
2451    
2452                    return a;
2453    
2454            }
2455    
2456            /**
2457             * _more_
2458             * 
2459             * @param c
2460             *            _more_
2461             * @param d
2462             *            _more_
2463             * 
2464             * @return _more_
2465             */
2466            public double getCircleAngleRange(EarthLocation c, EarthLocation d) {
2467    
2468                    double lat1 = c.getLatitude().getValue();
2469                    double lon1 = c.getLongitude().getValue();
2470                    LatLonPointImpl p1 = new LatLonPointImpl(lat1, lon1);
2471    
2472                    double lat2 = d.getLatitude().getValue();
2473                    double lon2 = d.getLongitude().getValue();
2474                    LatLonPointImpl p2 = new LatLonPointImpl(lat2, lon2);
2475    
2476                    LatLonProjection pj1 = new LatLonProjection();
2477                    ProjectionPoint pp1 = pj1.latLonToProj(p1);
2478                    LatLonProjection pj2 = new LatLonProjection();
2479                    ProjectionPoint pp2 = pj2.latLonToProj(p2);
2480                    double dx = pp2.getX() - pp1.getX();
2481                    double dy = pp2.getY() - pp1.getY();
2482    
2483                    double a = Math.atan2(dy, dx);
2484    
2485                    return a;
2486            }
2487    
2488            /**
2489             * _more_
2490             * 
2491             * @param c
2492             *            _more_
2493             * @param angle
2494             *            _more_
2495             * @param r
2496             *            _more_
2497             * @param dt
2498             *            _more_
2499             * 
2500             * @return _more_
2501             * 
2502             * @throws VisADException
2503             *             _more_
2504             */
2505            public StormTrackPoint[] getHalfCircleTrackPoint(EarthLocation c,
2506                            double angle, double r, DateTime dt) throws VisADException {
2507                    // return 10 track point
2508                    int size = 11;
2509    
2510                    StormTrackPoint[] track = new StormTrackPoint[size];
2511    
2512                    double lat0 = c.getLatitude().getValue();
2513                    double lon0 = c.getLongitude().getValue();
2514    
2515                    for (int i = 0; i < size; i++) {
2516                            double af = (angle + (i + 1) * 15 * Math.PI / 180.0) * 180.0
2517                                            / Math.PI;
2518                            // change angle to azimuth
2519                            if ((af <= 90) && (af >= 0)) {
2520                                    af = 90 - af;
2521                            } else if ((af > 90) && (af <= 180)) {
2522                                    af = 360 + (90 - af);
2523                            } else if ((af < 0) && (af >= -180)) {
2524                                    af = 90 - af;
2525                            } else if ((af > 180) && (af <= 360)) {
2526                                    af = 450 - af;
2527                            } else if ((af < -180) && (af >= -360)) {
2528                                    af = -270 - af;
2529                            }
2530    
2531                            LatLonPointImpl lp = Bearing.findPoint(lat0, lon0, af, r, null);
2532    
2533                            EarthLocation el = new EarthLocationLite(lp.getLatitude(), lp
2534                                            .getLongitude(), 0);
2535                            StormTrackPoint sp = new StormTrackPoint(el, dt, 0, null);
2536    
2537                            track[i] = sp;
2538                    }
2539    
2540                    return track;
2541            }
2542    
2543            /**
2544             * _more_
2545             * 
2546             * @param c
2547             *            _more_
2548             * @param angle
2549             *            _more_
2550             * @param r
2551             *            _more_
2552             * @param dt
2553             *            _more_
2554             * 
2555             * @return _more_
2556             * 
2557             * @throws VisADException
2558             *             _more_
2559             */
2560            public StormTrackPoint[] getHalfCircleTrackPointOld(EarthLocation c,
2561                            double angle, double r, DateTime dt) throws VisADException {
2562                    // return 10 track point
2563                    int size = 11;
2564    
2565                    StormTrackPoint[] track = new StormTrackPoint[size];
2566                    FlatEarth e = new FlatEarth();
2567                    ProjectionPointImpl p0 = e.latLonToProj(c.getLatitude().getValue(), c
2568                                    .getLongitude().getValue());
2569    
2570                    for (int i = 0; i < size; i++) {
2571                            double af = angle + i * 15 * Math.PI / 180.0;
2572                            double x = p0.getX() + r * Math.cos(af);
2573                            double y = p0.getY() + r * Math.sin(af);
2574    
2575                            ProjectionPoint pp = new ProjectionPointImpl(x, y);
2576                            LatLonPointImpl lp = new LatLonPointImpl();
2577                            FlatEarth e3 = new FlatEarth();
2578                            LatLonPoint lp11 = e3.projToLatLon(pp, lp);
2579                            EarthLocation el = new EarthLocationLite(lp11.getLatitude(), lp11
2580                                            .getLongitude(), 0);
2581                            StormTrackPoint sp = new StormTrackPoint(el, dt, 0, null);
2582    
2583                            track[i] = sp;
2584                    }
2585    
2586                    return track;
2587            }
2588    
2589    }