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