001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.control.cyclone;
030
031import java.awt.Color;
032import java.awt.Dimension;
033import java.awt.Rectangle;
034import java.awt.geom.Rectangle2D;
035import java.rmi.RemoteException;
036import java.text.DecimalFormat;
037import java.util.ArrayList;
038import java.util.Calendar;
039import java.util.Date;
040import java.util.Iterator;
041import java.util.List;
042import java.util.Vector;
043
044import javax.swing.JComponent;
045
046import edu.wisc.ssec.mcidasv.ui.ColorSwatchComponent;
047import ucar.unidata.data.grid.GridUtil;
048import ucar.unidata.data.point.PointOb;
049import ucar.unidata.data.point.PointObFactory;
050import ucar.unidata.data.storm.StormParam;
051import ucar.unidata.data.storm.StormTrack;
052import ucar.unidata.data.storm.StormTrackPoint;
053import ucar.unidata.data.storm.Way;
054import ucar.unidata.geoloc.Bearing;
055import ucar.unidata.geoloc.LatLonPoint;
056import ucar.unidata.geoloc.LatLonPointImpl;
057import ucar.unidata.geoloc.ProjectionPoint;
058import ucar.unidata.geoloc.ProjectionPointImpl;
059import ucar.unidata.geoloc.projection.FlatEarth;
060import ucar.unidata.geoloc.projection.LatLonProjection;
061import ucar.unidata.gis.SpatialGrid;
062import ucar.unidata.ui.colortable.ColorTableDefaults;
063import ucar.unidata.ui.symbol.StationModel;
064import ucar.unidata.util.ColorTable;
065import ucar.unidata.util.GuiUtils;
066import ucar.unidata.util.LogUtil;
067import ucar.unidata.util.Misc;
068import ucar.unidata.util.Range;
069import ucar.unidata.view.geoloc.NavigatedDisplay;
070import ucar.unidata.xml.XmlObjectStore;
071import ucar.visad.Util;
072import ucar.visad.display.CompositeDisplayable;
073import ucar.visad.display.Displayable;
074import ucar.visad.display.StationModelDisplayable;
075import ucar.visad.display.TrackDisplayable;
076import visad.CommonUnit;
077import visad.Data;
078import visad.DateTime;
079import visad.FieldImpl;
080import visad.FunctionType;
081import visad.Integer1DSet;
082import visad.Real;
083import visad.RealType;
084import visad.Set;
085import visad.SetType;
086import visad.TextType;
087import visad.Tuple;
088import visad.Unit;
089import visad.VisADException;
090import visad.georef.EarthLocation;
091import visad.georef.EarthLocationLite;
092
093/**
094 * 
095 * @author Unidata Development Team
096 * @version $Revision$
097 */
098
099public 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 ColorSwatchComponent 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
685                if (v.isEmpty()) {
686                        retField = new FieldImpl((FunctionType) pointObs.getType(),
687                                        new Integer1DSet(((SetType) domainSet.getType())
688                                                        .getDomain(), 1));
689                        retField.setSample(0, pointObs.getSample(0), false);
690                } else if (v.size() == numObs) {
691                        retField = pointObs; // all were in domain, just return input
692                } else {
693                        retField = new FieldImpl((FunctionType) pointObs.getType(),
694                                        new Integer1DSet(((SetType) domainSet.getType())
695                                                        .getDomain(), v.size()));
696                        retField.setSamples((PointOb[]) v.toArray(new PointOb[v.size()]),
697                                        false, false);
698                }
699
700                return retField;
701        }
702
703        /** decluttering filter factor */
704        private float declutterFilter = 1.0f;
705
706        /**
707         * _more_
708         * 
709         * @return _more_
710         */
711        public float getDeclutterFilter() {
712                return declutterFilter;
713        }
714
715        /**
716         * _more_
717         * 
718         * @return _more_
719         */
720        protected Rectangle2D getBounds() {
721                return calculateRectangle();
722        }
723
724        /**
725         * _more_
726         * 
727         * @return _more_
728         */
729        protected Rectangle2D calculateRectangle() {
730                try {
731                        Rectangle2D.Double box = stormDisplayState.getStormTrackControl()
732                                        .getNavigatedDisplay().getVisadBox();
733                        if (!box.isEmpty()) {
734                                // pad rectangle by 5%
735                                double deltaWidth = (double) (.05 * box.width);
736                                double deltaHeight = (double) (.05 * box.height);
737                                double newX = box.x - deltaWidth;
738                                double newY = box.y - deltaHeight;
739                                box.setRect(newX, newY, box.width + (2.0 * deltaWidth),
740                                                box.height + (2.0 * deltaHeight));
741                        }
742                        return box;
743                } catch (Exception excp) {
744                        LogUtil.logException("calculating Rectangle ", excp);
745                        return new Rectangle2D.Double(0, 0, 0, 0);
746                }
747        }
748
749        /**
750         * _more_
751         * 
752         * @return _more_
753         * 
754         * @throws Exception
755         *             _more_
756         */
757        public StationModelDisplayable getLabelDisplay() throws Exception {
758                if (labelDisplay == null) {
759                        StationModel sm = (way.isObservation() ? stormDisplayState
760                                        .getObsLayoutModel() : stormDisplayState
761                                        .getForecastLayoutModel());
762                        if (sm != null) {
763                                labelDisplay = new StationModelDisplayable("dots");
764                                labelDisplay.setRotateShapes(true);
765                                labelDisplay.setUseTimesInAnimation(false);
766                                addDisplayable(labelDisplay);
767                                labelDisplay.setScale(stormDisplayState.getStormTrackControl()
768                                                .getDisplayScale());
769                        }
770                }
771                return labelDisplay;
772        }
773
774        /**
775         * _more_
776         * 
777         * @return _more_
778         * 
779         * 
780         * @throws RemoteException
781         *             _more_
782         * @throws VisADException
783         *             _more_
784         */
785        public StationModelDisplayable getObsPointDisplay() throws VisADException,
786                        RemoteException {
787                if (obsPointDisplay == null) {
788                        obsPointDisplay = new StationModelDisplayable("dots");
789                        obsPointDisplay.setRotateShapes(true);
790                        obsPointDisplay.setUseTimesInAnimation(false);
791                        addDisplayable(obsPointDisplay);
792                        obsPointDisplay.setScale(stormDisplayState.getStormTrackControl()
793                                        .getDisplayScale());
794                }
795                return obsPointDisplay;
796        }
797
798        /**
799         * _more_
800         * 
801         * @throws Exception
802         *             _more_
803         */
804        public void initTrackDisplay() throws Exception {
805
806                trackDisplay = new TrackDisplayable("track_"
807                                + stormDisplayState.getStormInfo().getStormId()); // +
808                                                                                                                                        // stormDisplayState.getColorParam(this));
809                if (way.isObservation()) {
810                        trackDisplay.setLineWidth(3);
811                } else {
812                        trackDisplay.setLineWidth(2);
813                        trackDisplay.setUseTimesInAnimation(false);
814                }
815                // setTrackColor();
816                int cnt = holder.displayableCount();
817
818                for (int i = 0; i < cnt; i++) {
819                        Displayable dp = holder.getDisplayable(i);
820                        if (dp.getClass().isInstance(trackDisplay)) {
821                                TrackDisplayable dd = (TrackDisplayable) dp;
822                                if (dd.toString().equalsIgnoreCase(trackDisplay.toString())) {
823                                        holder.removeDisplayable(dp);
824                                        cnt = cnt - 1;
825                                }
826                        }
827                }
828
829                addDisplayable(trackDisplay);
830
831        }
832
833        /**
834         * _more_
835         * 
836         * @return _more_
837         * 
838         * @throws Exception
839         *             _more_
840         */
841        public TrackDisplayable getTrackDisplay() throws Exception {
842                if (trackDisplay == null) {
843                        initTrackDisplay();
844                }
845                return trackDisplay;
846        }
847
848        /**
849         * _more_
850         * 
851         * @return _more_
852         * 
853         * @throws Exception
854         *             _more_
855         */
856        public CompositeDisplayable getConesHolder() throws Exception {
857                if (conesHolder == null) {
858                        conesHolder = new CompositeDisplayable("cone_"
859                                        + stormDisplayState.getStormInfo().getStormId());
860                        conesHolder.setVisible(true);
861                        conesHolder.setUseTimesInAnimation(false);
862                        addDisplayable(conesHolder);
863                }
864                return conesHolder;
865        }
866
867        /**
868         * _more_
869         * 
870         * @return _more_
871         * 
872         * @throws Exception
873         *             _more_
874         */
875        public TrackDisplayable getRingsDisplay() throws Exception {
876                if (ringsDisplay == null) {
877                        ringsDisplay = new TrackDisplayable("ring_"
878                                        + stormDisplayState.getStormInfo().getStormId() + "_"
879                                        + getWay());
880                        ringsDisplay.setVisible(true);
881                        ringsDisplay.setUseTimesInAnimation(false);
882                        addDisplayable(ringsDisplay);
883                }
884                return ringsDisplay;
885        }
886
887        /**
888         * _more_
889         * 
890         * @param param
891         *            _more_
892         * @param mode
893         *            _more_
894         * 
895         * @return _more_
896         * 
897         * @throws Exception
898         *             _more_
899         */
900        public TrackDisplayable makeConeDisplay(StormParam param, int mode)
901                        throws Exception {
902                FieldImpl field = makeConeField(param, mode);
903                if (field == null) {
904                        return null;
905                }
906                TrackDisplayable coneDisplay = new TrackDisplayable("cone_"
907                                + stormDisplayState.getStormInfo().getStormId());
908                coneDisplay.setUseTimesInAnimation(false);
909                coneDisplay.setTrack(field);
910                coneDisplay.setUseTimesInAnimation(false);
911                return coneDisplay;
912        }
913
914        /**
915         * _more_
916         * 
917         * @param param
918         *            _more_
919         * @param mode
920         *            _more_
921         * 
922         * @return _more_
923         * 
924         * @throws Exception
925         *             _more_
926         */
927        public TrackDisplayable makeRingDisplay(StormParam param, int mode)
928                        throws Exception {
929                FieldImpl field = makeRingsField(param, mode);
930                if (field == null) {
931                        return null;
932                }
933                TrackDisplayable ringDisplay = new TrackDisplayable("ring_"
934                                + stormDisplayState.getStormInfo().getStormId());
935                ringDisplay.setUseTimesInAnimation(false);
936                ringDisplay.setTrack(field);
937                ringDisplay.setUseTimesInAnimation(false);
938                return ringDisplay;
939        }
940
941        /**
942         * _more_
943         * 
944         * @return _more_
945         */
946        protected JComponent getColorSwatch() {
947                if (colorSwatch == null) {
948                        XmlObjectStore store = stormDisplayState.getStormTrackControl().getIdv().getStore();
949                        colorSwatch = new ColorSwatchComponent(store, getColor(),
950                                        "Set track color") {
951                                public void setBackground(Color newColor) {
952                                        super.setBackground(newColor);
953                                        WayDisplayState.this.color = newColor;
954                                        try {
955                                                setTrackColor();
956                                                setConeColor();
957                                                setRingsColor();
958                                                setLabelColor();
959                                        } catch (Exception exc) {
960                                                LogUtil.logException("Setting color", exc);
961                                        }
962                                }
963                        };
964                        colorSwatch.setMinimumSize(new Dimension(15, 15));
965                        colorSwatch.setPreferredSize(new Dimension(15, 15));
966                }
967                return colorSwatch;
968        }
969
970        /**
971         * _more_
972         * 
973         * @return _more_
974         */
975        public float[][] getColorPalette() {
976                ColorTable ct = stormDisplayState.getColorTable(colorParam);
977                if (ct != null) {
978                        return stormDisplayState.getStormTrackControl()
979                                        .getColorTableForDisplayable(ct);
980                }
981                return getColorPalette(getColor());
982        }
983
984        /**
985         * _more_
986         * 
987         * @param c
988         *            _more_
989         * 
990         * @return _more_
991         */
992        public static float[][] getColorPalette(Color c) {
993                if (c == null) {
994                        c = Color.red;
995                }
996                return ColorTableDefaults.allOneColor(c, true);
997        }
998
999        /**
1000         * _more_
1001         * 
1002         * @throws Exception
1003         *             _more_
1004         */
1005        private void setTrackColor() throws Exception {
1006                if (trackDisplay != null) {
1007                        if (colorParam == null
1008                                        || colorParam.getName().equalsIgnoreCase("Fixed")) {
1009                                trackDisplay.setColor(getColor());
1010                        } else
1011                                trackDisplay.setColorPalette(getColorPalette());
1012                }
1013
1014        }
1015
1016        /**
1017         * _more_
1018         * 
1019         * @throws Exception
1020         *             _more_
1021         */
1022        private void setLabelColor() throws Exception {
1023                Color c = getColor();
1024                if (labelDisplay != null) { // && !Misc.equals(c,
1025                                                                        // labelDisplay.getColor())) {
1026                        labelDisplay.setColor(c);
1027                }
1028                if (obsPointDisplay != null) { // && !Misc.equals(c,
1029                                                                                // obsPointDisplay.getColor())) {
1030                        obsPointDisplay.setColor(c);
1031                }
1032        }
1033
1034        /**
1035         * _more_
1036         * 
1037         * @throws Exception
1038         *             _more_
1039         */
1040        private void setConeColor() throws Exception {
1041                if (conesHolder != null) {
1042                        conesHolder.setColorPalette(getColorPalette(getColor()));
1043                }
1044        }
1045
1046        /**
1047         * _more_
1048         * 
1049         * @throws Exception
1050         *             _more_
1051         */
1052        private void setRingsColor() throws Exception {
1053                if (ringsDisplay != null) {
1054                        ringsDisplay.setColor(getColor());
1055                }
1056        }
1057
1058        /**
1059         * _more_
1060         * 
1061         * @throws RemoteException
1062         *             _more_
1063         * @throws VisADException
1064         *             _more_
1065         */
1066        public void deactivate() throws VisADException, RemoteException {
1067                ringsDisplay = null;
1068                conesHolder = null;
1069                if (holder != null) {
1070                }
1071                trackDisplay = null;
1072                labelDisplay = null;
1073                obsPointDisplay = null;
1074                holder = null;
1075                tracks = new ArrayList<StormTrack>();
1076                times = new ArrayList<DateTime>();
1077        }
1078
1079        /** _more_ */
1080        private static TextType fhourType;
1081
1082        /** _more_ */
1083        private static TextType rhourType;
1084
1085        /** _more_ */
1086        private static TextType shourType;
1087
1088        /**
1089         * _more_
1090         * 
1091         * @param track
1092         *            _more_
1093         * 
1094         * @throws Exception
1095         *             _more_
1096         */
1097        public void addTrack(StormTrack track) throws Exception {
1098                tracks.add(track);
1099        }
1100
1101        /**
1102         * _more_
1103         * 
1104         * 
1105         * 
1106         * @param mode
1107         *            _more_
1108         * @return _more_
1109         * @throws Exception
1110         *             _more_
1111         */
1112
1113        protected FieldImpl makeTrackField(int mode) throws Exception {
1114                List<FieldImpl> fields = new ArrayList<FieldImpl>();
1115                List<DateTime> times = new ArrayList<DateTime>();
1116
1117                // Use a local list to hold the point obs so we don't run into a race
1118                // condition
1119                List<PointOb> localPointObs = new ArrayList<PointOb>();
1120                List<PointOb> localAllPointObs = new ArrayList<PointOb>();
1121                Data[] datas = new Data[tracks.size()];
1122                int i = 0;
1123                for (StormTrack track : tracks) {
1124                        FieldImpl field = stormDisplayState.getStormTrackControl()
1125                                        .makeTrackField(track, colorParam);
1126                        if (field == null) {
1127                                continue;
1128                        }
1129                        if (i == 0)
1130                                datas[i++] = field;
1131                        else
1132                                datas[i++] = field.changeMathType(datas[0].getType());
1133                        fields.add(field);
1134                        times.add(track.getStartTime());
1135                        // if(!way.isObservation() && mode == 0)
1136                        localPointObs.addAll(makePointObs(track, !way.isObservation()));
1137                        if (way.isObservation()) {
1138                                localAllPointObs.addAll(makeObsPointObs(track));
1139                        }
1140                }
1141
1142                pointObs = localPointObs;
1143                allPointObs = localAllPointObs;
1144
1145                if (fields.size() == 0) {
1146                        return null;
1147                }
1148                // if(fields.size()==1) return fields.get(0);
1149
1150                if (!way.isObservation() && (mode == 1)) {
1151                        return Util.indexedField(datas, false);
1152                } else {
1153                        return Util.makeTimeField(fields, times);
1154                }
1155        }
1156
1157        /**
1158         * _more_
1159         * 
1160         * 
1161         * @param stormParam
1162         *            _more_
1163         * @param mode
1164         *            _more_
1165         * 
1166         * @return _more_
1167         * @throws Exception
1168         *             _more_
1169         */
1170        protected FieldImpl makeConeField(StormParam stormParam, int mode)
1171                        throws Exception {
1172                List<FieldImpl> fields = new ArrayList<FieldImpl>();
1173                List<DateTime> times = new ArrayList<DateTime>();
1174                Data[] datas = new Data[tracks.size()];
1175                int i = 0;
1176                for (StormTrack track : tracks) {
1177                        StormTrack coneTrack = makeConeTrack(track, stormParam);
1178                        if (coneTrack == null) {
1179                                continue;
1180                        }
1181                        FieldImpl field = stormDisplayState.getStormTrackControl()
1182                                        .makeTrackField(coneTrack, null);
1183                        fields.add(field);
1184
1185                        times.add(track.getStartTime());
1186                        datas[i++] = field;
1187                }
1188
1189                if (fields.size() == 0) {
1190                        return null;
1191                }
1192
1193                if (!way.isObservation() && (mode == 1)) {
1194                        return Util.indexedField(datas, false);
1195                } else {
1196                        return Util.makeTimeField(fields, times);
1197                }
1198        }
1199
1200        /**
1201         * _more_
1202         * 
1203         * @param track
1204         *            _more_
1205         * @param useStartTime
1206         *            _more_
1207         * 
1208         * 
1209         * @return _more_
1210         * @throws Exception
1211         *             _more_
1212         */
1213        private List<PointOb> makePointObs(StormTrack track, boolean useStartTime)
1214                        throws Exception {
1215                boolean isObservation = way.isObservation();
1216                DateTime startTime = track.getStartTime();
1217                List<StormTrackPoint> stps = track.getTrackPoints();
1218                if (fhourType == null) {
1219                        fhourType = new TextType("fhour");
1220                }
1221
1222                if (rhourType == null) {
1223                        rhourType = new TextType("rhour");
1224                }
1225
1226                if (shourType == null) {
1227                        shourType = new TextType("shour");
1228                }
1229                List<PointOb> pointObs = new ArrayList<PointOb>();
1230
1231                DecimalFormat format = new DecimalFormat("0.#");
1232                Date startDate = Util.makeDate(startTime);
1233                List<StormParam> params = track.getParams();
1234                for (int i = 0; i < stps.size(); i++) {
1235                        StormTrackPoint stp = stps.get(i);
1236                        DateTime time = (useStartTime ? startTime : stp.getTime());
1237                        String flabel = "";
1238                        String rlabel = "";
1239                        String slabel = "";
1240                        if (!isObservation) {
1241                                if (i == 0) {
1242                                        // label = way.getId() + ": " + track.getStartTime();
1243                                } else {
1244                                        flabel = "" + stp.getForecastHour() + "H";
1245                                        Date dttm = Util.makeDate(stp.getTime());
1246                                        rlabel = "" + dttm.toString();
1247                                        slabel = "" + getMonDayHour(dttm);
1248                                        ;
1249                                }
1250                        } else if (useStartTime && (i > 0)) {
1251                                Date dttm = Util.makeDate(stp.getTime());
1252                                double diffSeconds = (dttm.getTime() - startDate.getTime()) / 1000.0;
1253                                double diffHours = diffSeconds / 3600.0;
1254
1255                                flabel = format.format(diffHours) + "H";
1256                                rlabel = "" + dttm.toString();
1257                                slabel = "" + getMonDayHour(dttm);
1258                        }
1259                        Data[] data = new Data[params.size() + 3];
1260
1261                        data[0] = new visad.Text(rhourType, rlabel);
1262                        data[1] = new visad.Text(fhourType, flabel);
1263                        data[2] = new visad.Text(shourType, slabel);
1264                        for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) {
1265                                Real r = stp.getAttribute(params.get(paramIdx));
1266                                if (r == null) {
1267                                        r = params.get(paramIdx).getReal(Double.NaN);
1268                                }
1269                                data[paramIdx + 3] = r;
1270
1271                        }
1272                        Tuple tuple = new Tuple(data);
1273                        pointObs.add(PointObFactory.makePointOb(stp.getLocation(), time,
1274                                        tuple));
1275
1276                }
1277                return pointObs;
1278        }
1279
1280        /**
1281         * _more_
1282         * 
1283         * @param dt
1284         *            _more_
1285         * 
1286         * @return _more_
1287         */
1288        private String getMonDayHour(Date dt) {
1289                Calendar cal = Calendar.getInstance();
1290
1291                cal.setTime(dt);
1292                int m = cal.get(Calendar.MONTH) + 1; // 0 base, ie 0 for Jan
1293                int d = cal.get(Calendar.DAY_OF_MONTH);
1294                int h = cal.get(Calendar.HOUR_OF_DAY);
1295
1296                return "" + m + "/" + d + "/" + h + "H";
1297        }
1298
1299        /**
1300         * _more_
1301         * 
1302         * @param track
1303         *            _more_
1304         * 
1305         * @return _more_
1306         * 
1307         * @throws Exception
1308         *             _more_
1309         */
1310        private List<PointOb> makeObsPointObs(StormTrack track) throws Exception {
1311                DateTime startTime = track.getStartTime();
1312                List<StormTrackPoint> stps = track.getTrackPoints();
1313                if (fhourType == null) {
1314                        fhourType = new TextType("fhour");
1315                }
1316                if (rhourType == null) {
1317                        rhourType = new TextType("rhour");
1318                }
1319                if (shourType == null) {
1320                        shourType = new TextType("shour");
1321                }
1322                List<PointOb> pointObs = new ArrayList<PointOb>();
1323                DecimalFormat format = new DecimalFormat("0.#");
1324                List<StormParam> params = track.getParams();
1325                for (int i = 0; i < stps.size(); i++) {
1326                        StormTrackPoint baseStp = stps.get(i);
1327                        DateTime baseTime = baseStp.getTime();
1328                        Date baseDate = Util.makeDate(baseTime);
1329                        for (int j = i; j < stps.size(); j++) {
1330                                StormTrackPoint stp = stps.get(j);
1331                                String flabel = "";
1332                                String rlabel = "";
1333                                String slabel = "";
1334                                if (j > 0) {
1335                                        Date dttm = Util.makeDate(stp.getTime());
1336                                        double diffSeconds = (dttm.getTime() - baseDate.getTime()) / 1000.0;
1337                                        double diffHours = diffSeconds / 3600.0;
1338                                        flabel = format.format(diffHours) + "H";
1339                                        rlabel = "" + dttm.toString();
1340                                        slabel = "" + getMonDayHour(dttm);
1341                                }
1342                                Data[] data = new Data[params.size() + 3];
1343                                data[0] = new visad.Text(fhourType, flabel);
1344                                data[1] = new visad.Text(rhourType, rlabel);
1345                                data[2] = new visad.Text(shourType, slabel);
1346                                for (int paramIdx = 0; paramIdx < params.size(); paramIdx++) {
1347                                        Real r = stp.getAttribute(params.get(paramIdx));
1348                                        if (r == null) {
1349                                                r = params.get(paramIdx).getReal(Double.NaN);
1350                                        }
1351                                        data[paramIdx + 3] = r;
1352                                }
1353                                Tuple tuple = new Tuple(data);
1354                                pointObs.add(PointObFactory.makePointOb(stp.getLocation(),
1355                                                baseTime, tuple));
1356                        }
1357                }
1358                return pointObs;
1359        }
1360
1361        /**
1362         * _more_
1363         * 
1364         * @return _more_
1365         */
1366        public List<StormTrack> getTracks() {
1367                return tracks;
1368        }
1369
1370        /**
1371         * _more_
1372         * 
1373         * @return _more_
1374         */
1375        public List<DateTime> getTimes() {
1376                return times;
1377        }
1378
1379        /**
1380         * _more_
1381         * 
1382         * @param displayable
1383         *            _more_
1384         * 
1385         * 
1386         * @throws RemoteException
1387         *             _more_
1388         * @throws VisADException
1389         *             _more_
1390         */
1391        public void addDisplayable(Displayable displayable) throws VisADException,
1392                        RemoteException {
1393                getHolder().addDisplayable(displayable);
1394        }
1395
1396        /**
1397         * _more_
1398         * 
1399         * @param displayable
1400         *            _more_
1401         * 
1402         * @throws RemoteException
1403         *             _more_
1404         * @throws VisADException
1405         *             _more_
1406         */
1407        public void removeDisplayable(Displayable displayable)
1408                        throws VisADException, RemoteException {
1409                getHolder().removeDisplayable(displayable);
1410        }
1411
1412        /**
1413         * Set the ConeState property.
1414         * 
1415         * @param value
1416         *            The new value for ConeState
1417         */
1418        public void setConeState(DisplayState value) {
1419                coneState = value;
1420        }
1421
1422        /**
1423         * Get the ConeState property.
1424         * 
1425         * @return The ConeState
1426         */
1427        public DisplayState getConeState() {
1428                return coneState;
1429        }
1430
1431        /**
1432         * Set the TrackState property.
1433         * 
1434         * @param value
1435         *            The new value for TrackState
1436         */
1437        public void setTrackState(DisplayState value) {
1438                trackState = value;
1439        }
1440
1441        /**
1442         * Get the TrackState property.
1443         * 
1444         * @return The TrackState
1445         */
1446        public DisplayState getTrackState() {
1447                return trackState;
1448        }
1449
1450        /**
1451         * Set the RingsState property.
1452         * 
1453         * @param value
1454         *            The new value for RingsState
1455         */
1456        public void setRingsState(DisplayState value) {
1457                ringsState = value;
1458        }
1459
1460        /**
1461         * Get the RingsState property.
1462         * 
1463         * @return The RingsState
1464         */
1465        public DisplayState getRingsState() {
1466                return ringsState;
1467        }
1468
1469        /**
1470         * Set the WayState property.
1471         * 
1472         * @param value
1473         *            The new value for WayState
1474         */
1475        public void setWayState(DisplayState value) {
1476                wayState = value;
1477        }
1478
1479        /**
1480         * Get the WayState property.
1481         * 
1482         * @return The WayState
1483         */
1484        public DisplayState getWayState() {
1485                return wayState;
1486        }
1487
1488        /**
1489         * Set the Color property.
1490         * 
1491         * @param value
1492         *            The new value for Color
1493         */
1494        public void setColor(Color value) {
1495                color = value;
1496        }
1497
1498        /**
1499         * Get the Color property.
1500         * 
1501         * @return The Color
1502         */
1503        public Color getColor() {
1504                return color;
1505        }
1506
1507        /**
1508         * Set the StormDisplayState property.
1509         * 
1510         * @param value
1511         *            The new value for StormDisplayState
1512         */
1513        public void setStormDisplayState(StormDisplayState value) {
1514                stormDisplayState = value;
1515        }
1516
1517        /**
1518         * Get the StormDisplayState property.
1519         * 
1520         * @return The StormDisplayState
1521         */
1522        public StormDisplayState getStormDisplayState() {
1523                return stormDisplayState;
1524        }
1525
1526        /**
1527         * Set the Way property.
1528         * 
1529         * @param value
1530         *            The new value for Way
1531         */
1532        public void setWay(Way value) {
1533                way = value;
1534        }
1535
1536        /**
1537         * Get the Way property.
1538         * 
1539         * @return The Way
1540         */
1541        public Way getWay() {
1542                return way;
1543        }
1544
1545        /**
1546         * Set the ColorTable property.
1547         * 
1548         * @param value
1549         *            The new value for ColorTable
1550         */
1551        public void setColorTable(String value) {
1552        }
1553
1554        /**
1555         * _more_
1556         * 
1557         * @param track
1558         *            _more_
1559         * @param param
1560         *            _more_
1561         * 
1562         * @return _more_
1563         */
1564        private List<StormTrackPoint> getRealTrackPoints(StormTrack track,
1565                        StormParam param) {
1566                List<StormTrackPoint> newStps = new ArrayList();
1567                List<StormTrackPoint> stps = track.getTrackPoints();
1568
1569                newStps.add(stps.get(0));
1570                Iterator<StormTrackPoint> it = stps.iterator();
1571
1572                while (it.hasNext()) {
1573                        StormTrackPoint stp = it.next();
1574                        if (stp.getAttribute(param) != null) {
1575                                newStps.add(stp);
1576                        }
1577                }
1578                return newStps;
1579        }
1580
1581        /**
1582         * _more_
1583         * 
1584         * @param stormParam
1585         *            _more_
1586         * @param mode
1587         *            _more_
1588         * 
1589         * @return _more_
1590         * 
1591         * @throws Exception
1592         *             _more_
1593         */
1594        protected FieldImpl makeRingsField(StormParam stormParam, int mode)
1595                        throws Exception {
1596                List<FieldImpl> fields = new ArrayList<FieldImpl>();
1597                List<DateTime> times = new ArrayList<DateTime>();
1598                Data[] datas = new Data[tracks.size() * 10];
1599                int i = 0;
1600
1601                if (!way.isObservation() && (mode == 1)) {
1602                        for (StormTrack track : tracks) {
1603                                List<StormTrack> stList = makeRingTrackList(track, stormParam);
1604                                for (StormTrack stk : stList) {
1605                                        FieldImpl field = stormDisplayState.getStormTrackControl()
1606                                                        .makeTrackField(stk, null);
1607                                        fields.add(field);
1608                                        datas[i++] = field;
1609                                }
1610
1611                        }
1612
1613                        return Util.indexedField(datas, false);
1614                }
1615
1616                for (StormTrack track : tracks) {
1617                        FieldImpl ringField = makeRingTracks(track, stormParam);
1618                        fields.add(ringField);
1619                        times.add(track.getStartTime());
1620                }
1621
1622                if (fields.size() == 0) {
1623                        return null;
1624                }
1625
1626                return Util.makeTimeField(fields, times);
1627        }
1628
1629        /**
1630         * _more_
1631         * 
1632         * @param track
1633         *            _more_
1634         * @param param
1635         *            _more_
1636         * 
1637         * @return _more_
1638         * 
1639         * @throws Exception
1640         *             _more_
1641         */
1642        public List makeRingTrackList(StormTrack track, StormParam param)
1643                        throws Exception {
1644                List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1645                List<StormTrack> stracks = new ArrayList();
1646
1647                int size = stps.size();
1648                DateTime dt = stps.get(0).getTime();
1649                Way ringWay = new Way(getWay() + "_RING");
1650                int numberOfPoints = 73;
1651                double angleDelta = 360.0 / (numberOfPoints - 1);
1652                for (int i = 0; i < size; i++) {
1653                        StormTrackPoint stp = stps.get(i);
1654                        Real r = stp.getAttribute(param);
1655                        if (r != null) {
1656                                double rr = r.getValue();
1657                                double azi = 0.0;
1658                                List ringList = new ArrayList<StormTrackPoint>();
1659                                for (int j = 0; j < numberOfPoints; j++) {
1660                                        ringList.add(getCirclePoint(stp, rr, azi, dt));
1661                                        azi = azi + angleDelta;
1662                                }
1663                                stracks.add(new StormTrack(track.getStormInfo(), ringWay,
1664                                                ringList, null));
1665                        }
1666                }
1667
1668                return stracks;
1669
1670        }
1671
1672        /**
1673         * _more_
1674         * 
1675         * @param track
1676         *            _more_
1677         * @param param
1678         *            _more_
1679         * 
1680         * @return _more_
1681         * 
1682         * 
1683         * @throws Exception
1684         *             _more_
1685         */
1686        public FieldImpl makeRingTracks(StormTrack track, StormParam param)
1687                        throws Exception {
1688                List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1689                List<StormTrack> stracks = new ArrayList();
1690                int size = stps.size();
1691                DateTime dt = stps.get(0).getTime();
1692                Way ringWay = new Way(getWay() + "_RING");
1693                int numberOfPoints = 73;
1694                double angleDelta = 360.0 / (numberOfPoints - 1);
1695                for (int i = 0; i < size; i++) {
1696                        StormTrackPoint stp = stps.get(i);
1697                        Real r = stp.getAttribute(param);
1698                        if (r != null) {
1699                                double rr = r.getValue();
1700                                double azi = 0.0;
1701                                List ringList = new ArrayList<StormTrackPoint>();
1702                                for (int j = 0; j < numberOfPoints; j++) {
1703                                        ringList.add(getCirclePoint(stp, rr, azi, dt));
1704                                        azi = azi + angleDelta;
1705                                }
1706                                stracks.add(new StormTrack(track.getStormInfo(), ringWay,
1707                                                ringList, null));
1708                        }
1709                }
1710
1711                Data[] datas = new Data[stracks.size()];
1712                int i = 0;
1713                for (StormTrack ringTrack : stracks) {
1714                        datas[i++] = stormDisplayState.getStormTrackControl()
1715                                        .makeTrackField(ringTrack, null);
1716                }
1717                return Util.indexedField(datas, false);
1718        }
1719
1720        /**
1721         * _more_
1722         * 
1723         * @param stp
1724         *            _more_
1725         * @param r0
1726         *            _more_
1727         * @param azimuth
1728         *            _more_
1729         * @param dt
1730         *            _more_
1731         * 
1732         * @return _more_
1733         * 
1734         * @throws VisADException
1735         *             _more_
1736         */
1737        public StormTrackPoint getCirclePoint(StormTrackPoint stp, double r0,
1738                        double azimuth, DateTime dt) throws VisADException {
1739                //
1740
1741                EarthLocation el = stp.getLocation();
1742                double lat0 = el.getLatitude().getValue();
1743                double lon0 = el.getLongitude().getValue();
1744                // DateTime dt = stp.getTime();
1745                LatLonPointImpl lp = Bearing.findPoint(lat0, lon0, azimuth, r0, null);
1746
1747                EarthLocation el1 = new EarthLocationLite(lp.getLatitude(), lp
1748                                .getLongitude(), 0);
1749                StormTrackPoint stp1 = new StormTrackPoint(el1, dt, 0, null);
1750
1751                return stp1;
1752        }
1753
1754        /**
1755         * old
1756         * 
1757         * @param track
1758         *            _more_
1759         * @param param
1760         *            _more_
1761         * 
1762         * @return _more_
1763         * 
1764         * @throws VisADException
1765         *             _more_
1766         */
1767        public StormTrack makeConeTrack_Old(StormTrack track, StormParam param)
1768                        throws VisADException {
1769                List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1770                int size = stps.size();
1771                int numberOfPoint = size * 2 + 11;
1772                StormTrackPoint[] conePoints = new StormTrackPoint[numberOfPoint];
1773
1774                StormTrackPoint stp1 = stps.get(0);
1775                conePoints[0] = stp1; // first point & last point
1776                conePoints[numberOfPoint - 1] = stp1;
1777
1778                StormTrackPoint stp2;
1779                StormTrackPoint stp;
1780
1781                // circle 1 to n
1782
1783                for (int i = 1; i < size; i++) {
1784                        stp2 = stps.get(i);
1785                        // right point
1786                        stp = getPointToCircleTangencyPoint(stp1, stp2, param, true);
1787                        conePoints[i] = stp;
1788                        // left point
1789                        stp = getPointToCircleTangencyPoint(stp1, stp2, param, false);
1790                        conePoints[numberOfPoint - i - 1] = stp;
1791                        stp1 = stp2;
1792                }
1793
1794                // end point half circle take 11 points
1795                StormTrackPoint last = stps.get(size - 1);
1796                EarthLocation lastEl = last.getLocation();
1797                StormTrackPoint endSTP = conePoints[size - 1];
1798                int ii = 0;
1799                while ((endSTP == null) && (ii < (size - 2))) {
1800                        ii++;
1801                        last = stps.get(size - 1 - ii);
1802                        lastEl = last.getLocation();
1803                        endSTP = conePoints[size - 1 - ii];
1804                }
1805
1806                if ((endSTP == null) || (ii == (size - 2))) {
1807                        return null;
1808                }
1809                EarthLocation endEl = endSTP.getLocation();
1810
1811                double ang = getCircleAngleRange(lastEl, endEl);
1812
1813                Real r = last.getAttribute(param);
1814                StormTrackPoint[] halfCircle = getHalfCircleTrackPoint(lastEl, ang,
1815                                ((r != null) ? r.getValue() : 0), last.getTime());
1816
1817                for (int i = 0; i < 11; i++) {
1818                        conePoints[size + i] = halfCircle[i];
1819                }
1820
1821                List coneList = new ArrayList<StormTrackPoint>();
1822                for (int i = 0; i < numberOfPoint; i++) {
1823                        if (conePoints[i] != null) {
1824                                coneList.add(conePoints[i]);
1825                        }
1826                }
1827
1828                return new StormTrack(track.getStormInfo(),
1829                                new Way(getWay() + "_CONE"), coneList, null);
1830
1831        }
1832
1833        /**
1834         * construct the cone track as track of point to circle and circle to circle
1835         * 
1836         * @param track
1837         *            _more_
1838         * @param param
1839         *            _more_
1840         * 
1841         * @return _more_
1842         * 
1843         * @throws VisADException
1844         *             _more_
1845         */
1846        public StormTrack makeConeTrack(StormTrack track, StormParam param)
1847                        throws VisADException {
1848
1849                List<StormTrackPoint> stps = getRealTrackPoints(track, param);
1850                int size = stps.size();
1851                int numberOfPoint = size * 2 + 100;
1852                List<StormTrackPoint> conePointsLeft = new ArrayList<StormTrackPoint>();
1853                List<StormTrackPoint> conePointsRight = new ArrayList<StormTrackPoint>();
1854
1855                StormTrackPoint stp1 = stps.get(0);
1856                conePointsRight.add(stp1); // first point & last point
1857                conePointsLeft.add(stp1);
1858                StormTrackPoint stp2 = stps.get(1);
1859                StormTrackPoint stp3 = stps.get(2);
1860                int nn = 3;
1861                // first point to circle
1862                List<StormTrackPoint> p2c = getPointToCircleTangencyPointA(stp1, stp2,
1863                                stp3, param, true);
1864                while (p2c == null) { // need to find the first point with param value
1865                        stp2 = stp3;
1866                        if (nn < size) {
1867                                stp3 = stps.get(nn);
1868                        } else {
1869                                stp3 = null;
1870                                // return null;
1871                        }
1872                        p2c = getPointToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1873                        nn++;
1874                        if (nn >= size) {
1875                                break;
1876                        }
1877                }
1878                if (p2c != null) {
1879                        conePointsRight.addAll(p2c);
1880                        p2c = getPointToCircleTangencyPointA(stp1, stp2, stp3, param, false);
1881                        conePointsLeft.addAll(p2c);
1882                }
1883
1884                // circle to circle 1 to n
1885                stp1 = stp2;
1886                stp2 = stp3;
1887                for (int i = nn; i < size; i++) {
1888                        stp3 = stps.get(i);
1889                        // right point
1890                        p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1891                        if (p2c != null) {
1892                                conePointsRight.addAll(p2c);
1893                                // left point
1894                                p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param,
1895                                                false);
1896                                conePointsLeft.addAll(p2c);
1897                                stp1 = stp2; // update the first point only after the valid
1898                                                                // second point
1899                        }
1900
1901                        stp2 = stp3;
1902                }
1903                // last circle
1904                stp3 = null;
1905                p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param, true);
1906                if (p2c != null) {
1907                        conePointsRight.addAll(p2c);
1908                        // left point
1909                        p2c = getCircleToCircleTangencyPointA(stp1, stp2, stp3, param,
1910                                        false);
1911                        conePointsLeft.addAll(p2c);
1912                        stp1 = stp2;
1913                }
1914
1915                // end point half circle take 11 points
1916                StormTrackPoint last = stp2;
1917                if (last == null) {
1918                        last = stp1;
1919                }
1920                if (last == null) {
1921                        return null;
1922                }
1923                EarthLocation lastEl = last.getLocation();
1924                StormTrackPoint endSTP = conePointsRight
1925                                .get(conePointsRight.size() - 1);
1926                /*
1927                 * int ii = 0; while ((endSTP == null) && (ii < (size - 2))) { ii++;
1928                 * last = stps.get(size - 1 - ii); lastEl = last.getLocation(); endSTP =
1929                 * conePointsRight.get(size - 1 - ii); }
1930                 * 
1931                 * if ((endSTP == null) || (ii == (size - 2))) { return null; }
1932                 */
1933                if (endSTP == null) {
1934                        return null;
1935                }
1936                EarthLocation endEl = endSTP.getLocation();
1937
1938                double ang = getCircleAngleRange(lastEl, endEl);
1939
1940                Real r = last.getAttribute(param);
1941                StormTrackPoint[] halfCircle = getHalfCircleTrackPoint(lastEl, ang,
1942                                ((r != null) ? r.getValue() : 0), last.getTime());
1943
1944                for (int i = 0; i < 11; i++) {
1945                }
1946                // merge three lists
1947                List<StormTrackPoint> coneList = new ArrayList<StormTrackPoint>();
1948                int s1 = conePointsRight.size();
1949                for (int i = 0; i < s1; i++) {
1950                        if (conePointsRight.get(i) != null) {
1951                                coneList.add(conePointsRight.get(i));
1952                        }
1953                }
1954                for (int i = 0; i < 11; i++) {
1955                        coneList.add(halfCircle[i]);
1956                }
1957                int s2 = conePointsLeft.size();
1958                for (int i = s2; i > 0; i--) {
1959                        if (conePointsLeft.get(i - 1) != null) {
1960                                coneList.add(conePointsLeft.get(i - 1));
1961                        }
1962                }
1963
1964                return new StormTrack(track.getStormInfo(),
1965                                new Way(getWay() + "_CONE"), coneList, null);
1966
1967        }
1968
1969        /**
1970         * calculate the bearing of two storm track points
1971         * 
1972         * @param sp1
1973         *            _more_
1974         * @param sp2
1975         *            _more_
1976         * 
1977         * @return _more_
1978         */
1979        public Bearing getStormPoinsBearing(StormTrackPoint sp1, StormTrackPoint sp2) {
1980                EarthLocation el1 = sp1.getLocation();
1981                EarthLocation el2 = sp2.getLocation();
1982                return Bearing.calculateBearing(el1.getLatitude().getValue(), el1
1983                                .getLongitude().getValue(), el2.getLatitude().getValue(), el2
1984                                .getLongitude().getValue(), null);
1985
1986        }
1987
1988        /**
1989         * get the tangency point to the circle of the second point and the third
1990         * point as its direction of adding additional points
1991         * 
1992         * @param sp1
1993         *            outside point
1994         * @param sp2
1995         *            the center of the circle
1996         * @param sp3
1997         *            _more_
1998         * @param param
1999         *            _more_
2000         * @param right
2001         *            _more_
2002         * 
2003         * @return _more_
2004         * 
2005         * @throws VisADException
2006         *             _more_
2007         */
2008        public List<StormTrackPoint> getPointToCircleTangencyPointA(
2009                        StormTrackPoint sp1, StormTrackPoint sp2, StormTrackPoint sp3,
2010                        StormParam param, boolean right) throws VisADException {
2011
2012                List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2013                if (sp3 == null) {
2014                        return getPointToCircleTangencyPointB(sp1, sp2, param, right);
2015                }
2016
2017                EarthLocation el1 = sp1.getLocation();
2018                EarthLocation el2 = sp2.getLocation();
2019                EarthLocation el3 = sp3.getLocation();
2020
2021                Real rl = sp2.getAttribute(param);
2022                double r = rl.getValue();
2023
2024                if (Float.isNaN((float) r) || (r == 0.0)) {
2025                        return null;
2026                }
2027
2028                double lat1 = el1.getLatitude().getValue();
2029                double lon1 = el1.getLongitude().getValue();
2030
2031                double lat2 = el2.getLatitude().getValue();
2032                double lon2 = el2.getLongitude().getValue();
2033
2034                double lat3 = el3.getLatitude().getValue();
2035                double lon3 = el3.getLongitude().getValue();
2036
2037                Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2038                Bearing c = Bearing.calculateBearing(lat2, lon2, lat3, lon3, null);
2039                double dist1 = b.getDistance();
2040
2041                if (dist1 < r) { // first point is inside the circle
2042                        trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2043                                        right));
2044                        return trackPoints;
2045                }
2046
2047                double af = getCircleAngleRange(el1, el2);
2048                double ddt = Math.abs(b.getAngle() - c.getAngle());
2049                double bt = getCircleTangencyAngle(el1, el2, r);
2050
2051                af = af * 180.0 / Math.PI;
2052                bt = bt * 180.0 / Math.PI;
2053                if (right) {
2054                        af = af - 90;
2055                } else {
2056                        af = af + 90;
2057                }
2058                // change angle to azimuth
2059                double az = af;
2060                if ((af <= 90) && (af >= 0)) {
2061                        az = 90 - af;
2062                } else if ((af > 90) && (af <= 180)) {
2063                        az = 360 + (90 - af);
2064                } else if ((af < 0) && (af >= -180)) {
2065                        az = 90 - af;
2066                } else if ((af > 180) && (af <= 360)) {
2067                        az = 450 - af;
2068                } else if ((af < -180) && (af >= -360)) {
2069                        az = -270 - af;
2070                }
2071                if (right) {
2072                        az = az + bt;
2073                } else {
2074                        az = az - bt;
2075                }
2076
2077                if (ddt > 270) {
2078                        ddt = 360 - ddt;
2079                } else if (ddt > 180) {
2080                        ddt = ddt - 180;
2081                } else if (ddt > 90) {
2082                        ddt = ddt - 90;
2083                }
2084
2085                double dt = bt;
2086
2087                if (right) {
2088                        if ((c.getAngle() < b.getAngle())
2089                                        && (Math.abs(b.getAngle() - c.getAngle()) < 90)) {
2090                                dt = bt + ddt;
2091                        } else if ((c.getAngle() > b.getAngle())
2092                                        && (Math.abs(b.getAngle() - c.getAngle()) > 180)) {
2093                                dt = bt + ddt;
2094                        } else {
2095                                dt = bt - ddt;
2096                        }
2097                } else {
2098                        if ((c.getAngle() > b.getAngle())
2099                                        && (Math.abs(b.getAngle() - c.getAngle()) < 90)) {
2100                                dt = bt + ddt;
2101                        } else if ((c.getAngle() < b.getAngle())
2102                                        && (Math.abs(b.getAngle() - c.getAngle()) > 180)) {
2103                                dt = bt + ddt;
2104                        } else {
2105                                dt = bt - ddt;
2106                        }
2107
2108                }
2109
2110                int n = (int) dt / 5 + 1;
2111                if (n <= 0) {
2112                        n = 1;
2113                }
2114                double dtt = dt / n;
2115                if (dtt < 0) {
2116                        dtt = 0;
2117                        n = 1;
2118                }
2119                for (int i = 0; i < n; i++) {
2120
2121                        LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2122                        // add more points along the circle
2123
2124                        EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2125                                        .getLongitude(), 0);
2126                        trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2127                        if (right) {
2128                                az = az - dtt;
2129                        } else {
2130                                az = az + dtt;
2131                        }
2132                }
2133
2134                return trackPoints;
2135        }
2136
2137        /**
2138         * get the tangency point to the circle of the second point
2139         * 
2140         * @param sp1
2141         *            _more_
2142         * @param sp2
2143         *            _more_
2144         * @param param
2145         *            _more_
2146         * @param right
2147         *            _more_
2148         * 
2149         * @return _more_
2150         * 
2151         * @throws VisADException
2152         *             _more_
2153         */
2154        public List<StormTrackPoint> getPointToCircleTangencyPointB(
2155                        StormTrackPoint sp1, StormTrackPoint sp2, StormParam param,
2156                        boolean right) throws VisADException {
2157
2158                List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2159
2160                if (sp2 == null) {
2161                        return null;
2162                }
2163                EarthLocation el1 = sp1.getLocation();
2164                EarthLocation el2 = sp2.getLocation();
2165
2166                Real rl = sp2.getAttribute(param);
2167                double r = rl.getValue();
2168
2169                if (Float.isNaN((float) r) || (r == 0.0)) {
2170                        return null;
2171                }
2172
2173                double lat1 = el1.getLatitude().getValue();
2174                double lon1 = el1.getLongitude().getValue();
2175
2176                double lat2 = el2.getLatitude().getValue();
2177                double lon2 = el2.getLongitude().getValue();
2178
2179                Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2180                double dist1 = b.getDistance();
2181
2182                if (dist1 < r) { // first point is inside the circle
2183                        trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2184                                        right));
2185                        return trackPoints;
2186                }
2187
2188                double af = getCircleAngleRange(el1, el2);
2189                double bt = getCircleTangencyAngle(el1, el2, r);
2190
2191                af = af * 180.0 / Math.PI;
2192                bt = bt * 180.0 / Math.PI;
2193                if (right) {
2194                        af = af - 90;
2195                } else {
2196                        af = af + 90;
2197                }
2198                // change angle to azimuth
2199                double az = af;
2200                if ((af <= 90) && (af >= 0)) {
2201                        az = 90 - af;
2202                } else if ((af > 90) && (af <= 180)) {
2203                        az = 360 + (90 - af);
2204                } else if ((af < 0) && (af >= -180)) {
2205                        az = 90 - af;
2206                } else if ((af > 180) && (af <= 360)) {
2207                        az = 450 - af;
2208                } else if ((af < -180) && (af >= -360)) {
2209                        az = -270 - af;
2210                }
2211                if (right) {
2212                        az = az + bt;
2213                } else {
2214                        az = az - bt;
2215                }
2216
2217                double dt = bt;
2218
2219                int n = (int) dt / 5 + 1;
2220                double dtt = dt / n;
2221                for (int i = 0; i < n; i++) {
2222
2223                        LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2224                        // add more points along the circle
2225
2226                        EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2227                                        .getLongitude(), 0);
2228                        trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2229                        if (right) {
2230                                az = az - dtt;
2231                        } else {
2232                                az = az + dtt;
2233                        }
2234                }
2235
2236                return trackPoints;
2237        }
2238
2239        /**
2240         * get the approximate tangency points of circle to the circle
2241         * 
2242         * @param sp1
2243         *            outside point
2244         * @param sp2
2245         *            the center of the circle
2246         * @param sp3
2247         *            _more_
2248         * @param param
2249         *            _more_
2250         * @param right
2251         *            _more_
2252         * 
2253         * @return _more_
2254         * 
2255         * @throws VisADException
2256         *             _more_
2257         */
2258        public List<StormTrackPoint> getCircleToCircleTangencyPointA(
2259                        StormTrackPoint sp1, StormTrackPoint sp2, StormTrackPoint sp3,
2260                        StormParam param, boolean right) throws VisADException {
2261
2262                List<StormTrackPoint> trackPoints = new ArrayList<StormTrackPoint>();
2263                if (sp3 == null) {
2264                        if (sp2 == null) {
2265                                return null;
2266                        }
2267                        trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2268                                        right));
2269                        return trackPoints;
2270                }
2271
2272                EarthLocation el1 = sp1.getLocation();
2273                EarthLocation el2 = sp2.getLocation();
2274                EarthLocation el3 = sp3.getLocation();
2275
2276                Real rl = sp2.getAttribute(param);
2277                double r = rl.getValue();
2278
2279                if (Float.isNaN((float) r) || (r == 0.0)) {
2280                        return null;
2281                }
2282
2283                double lat1 = el1.getLatitude().getValue();
2284                double lon1 = el1.getLongitude().getValue();
2285
2286                double lat2 = el2.getLatitude().getValue();
2287                double lon2 = el2.getLongitude().getValue();
2288
2289                double lat3 = el3.getLatitude().getValue();
2290                double lon3 = el3.getLongitude().getValue();
2291
2292                Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2293                double dist1 = b.getDistance();
2294                Bearing c = Bearing.calculateBearing(lat2, lon2, lat3, lon3, null);
2295                double x = Math.abs(c.getAngle() - b.getAngle());
2296
2297                if (right) {
2298                        if ((c.getAngle() > b.getAngle()) || (x > 180)) {
2299                                trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2300                                                right));
2301                                return trackPoints;
2302                        }
2303                }
2304
2305                if (!right) {
2306                        if ((c.getAngle() < b.getAngle()) && (x < 90)) {
2307                                trackPoints.add(getPointToCircleTangencyPoint(sp1, sp2, param,
2308                                                right));
2309                                return trackPoints;
2310                        }
2311                }
2312                double af = getCircleAngleRange(el1, el2);
2313                double dt = 0; // = Math.abs(b.getAngle() - c.getAngle());
2314
2315                if (x > 270) {
2316                        dt = 360 - x;
2317                } else if (x > 180) {
2318                        dt = x - 180;
2319                } else if (x > 90) {
2320                        dt = x - 90;
2321                } else {
2322                        dt = x;
2323                }
2324
2325                af = af * 180.0 / Math.PI;
2326                if (right) {
2327                        af = af - 90;
2328                } else {
2329                        af = af + 90;
2330                }
2331                // change angle to azimuth
2332                double az = af;
2333                if ((af <= 90) && (af >= 0)) {
2334                        az = 90 - af;
2335                } else if ((af > 90) && (af <= 180)) {
2336                        az = 360 + (90 - af);
2337                } else if ((af < 0) && (af >= -180)) {
2338                        az = 90 - af;
2339                } else if ((af > 180) && (af <= 360)) {
2340                        az = 450 - af;
2341                } else if ((af < -180) && (af >= -360)) {
2342                        az = -270 - af;
2343                }
2344
2345                int n = (int) dt / 5 + 1;
2346                double dtt = dt / n;
2347
2348                for (int i = 0; i < n; i++) {
2349
2350                        LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, az, r, null);
2351                        // add more points along the circle
2352
2353                        EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2354                                        .getLongitude(), 0);
2355                        trackPoints.add(new StormTrackPoint(el, sp1.getTime(), 0, null));
2356                        if (right) {
2357                                az = az - dtt;
2358                        } else {
2359                                az = az + dtt;
2360                        }
2361                }
2362
2363                return trackPoints;
2364        }
2365
2366        /**
2367         * get the 90 degree point to the line of the two points
2368         * 
2369         * @param sp1
2370         *            _more_
2371         * @param sp2
2372         *            _more_
2373         * @param param
2374         *            _more_
2375         * @param right
2376         *            _more_
2377         * 
2378         * @return _more_
2379         * 
2380         * @throws VisADException
2381         *             _more_
2382         */
2383        public StormTrackPoint getPointToCircleTangencyPoint(StormTrackPoint sp1,
2384                        StormTrackPoint sp2, StormParam param, boolean right)
2385                        throws VisADException {
2386
2387                EarthLocation el1 = sp1.getLocation();
2388                EarthLocation el2 = sp2.getLocation();
2389
2390                Real rl = sp2.getAttribute(param);
2391                double r = rl.getValue();
2392
2393                if (Float.isNaN((float) r) || (r == 0.0)) {
2394                        return null;
2395                }
2396
2397                double lat2 = el2.getLatitude().getValue();
2398                double lon2 = el2.getLongitude().getValue();
2399
2400                double af = getCircleAngleRange(el1, el2);
2401                af = af * 180.0 / Math.PI;
2402                if (right) {
2403                        af = af - 90;
2404                } else {
2405                        af = af + 90;
2406                }
2407                // change angle to azimuth
2408                if ((af <= 90) && (af >= 0)) {
2409                        af = 90 - af;
2410                } else if ((af > 90) && (af <= 180)) {
2411                        af = 360 + (90 - af);
2412                } else if ((af < 0) && (af >= -180)) {
2413                        af = 90 - af;
2414                } else if ((af > 180) && (af <= 360)) {
2415                        af = 450 - af;
2416                } else if ((af < -180) && (af >= -360)) {
2417                        af = -270 - af;
2418                }
2419
2420                LatLonPointImpl lp1 = Bearing.findPoint(lat2, lon2, af, r, null);
2421
2422                EarthLocation el = new EarthLocationLite(lp1.getLatitude(), lp1
2423                                .getLongitude(), 0);
2424                StormTrackPoint sp = new StormTrackPoint(el, sp1.getTime(), 0, null);
2425                return sp;
2426        }
2427
2428        /**
2429         * _more_
2430         * 
2431         * @param c
2432         *            _more_
2433         * @param d
2434         *            _more_
2435         * @param r
2436         *            _more_
2437         * 
2438         * @return _more_
2439         */
2440        public double getCircleTangencyAngle(EarthLocation c, EarthLocation d,
2441                        double r) {
2442
2443                double lat1 = c.getLatitude().getValue();
2444                double lon1 = c.getLongitude().getValue();
2445
2446                double lat2 = d.getLatitude().getValue();
2447                double lon2 = d.getLongitude().getValue();
2448
2449                Bearing b = Bearing.calculateBearing(lat1, lon1, lat2, lon2, null);
2450                double dist = b.getDistance();
2451                double a = Math.asin(r / dist);
2452
2453                return a;
2454
2455        }
2456
2457        /**
2458         * _more_
2459         * 
2460         * @param c
2461         *            _more_
2462         * @param d
2463         *            _more_
2464         * 
2465         * @return _more_
2466         */
2467        public double getCircleAngleRange(EarthLocation c, EarthLocation d) {
2468
2469                double lat1 = c.getLatitude().getValue();
2470                double lon1 = c.getLongitude().getValue();
2471                LatLonPointImpl p1 = new LatLonPointImpl(lat1, lon1);
2472
2473                double lat2 = d.getLatitude().getValue();
2474                double lon2 = d.getLongitude().getValue();
2475                LatLonPointImpl p2 = new LatLonPointImpl(lat2, lon2);
2476
2477                LatLonProjection pj1 = new LatLonProjection();
2478                ProjectionPoint pp1 = pj1.latLonToProj(p1);
2479                LatLonProjection pj2 = new LatLonProjection();
2480                ProjectionPoint pp2 = pj2.latLonToProj(p2);
2481                double dx = pp2.getX() - pp1.getX();
2482                double dy = pp2.getY() - pp1.getY();
2483
2484                double a = Math.atan2(dy, dx);
2485
2486                return a;
2487        }
2488
2489        /**
2490         * _more_
2491         * 
2492         * @param c
2493         *            _more_
2494         * @param angle
2495         *            _more_
2496         * @param r
2497         *            _more_
2498         * @param dt
2499         *            _more_
2500         * 
2501         * @return _more_
2502         * 
2503         * @throws VisADException
2504         *             _more_
2505         */
2506        public StormTrackPoint[] getHalfCircleTrackPoint(EarthLocation c,
2507                        double angle, double r, DateTime dt) throws VisADException {
2508                // return 10 track point
2509                int size = 11;
2510
2511                StormTrackPoint[] track = new StormTrackPoint[size];
2512
2513                double lat0 = c.getLatitude().getValue();
2514                double lon0 = c.getLongitude().getValue();
2515
2516                for (int i = 0; i < size; i++) {
2517                        double af = (angle + (i + 1) * 15 * Math.PI / 180.0) * 180.0
2518                                        / Math.PI;
2519                        // change angle to azimuth
2520                        if ((af <= 90) && (af >= 0)) {
2521                                af = 90 - af;
2522                        } else if ((af > 90) && (af <= 180)) {
2523                                af = 360 + (90 - af);
2524                        } else if ((af < 0) && (af >= -180)) {
2525                                af = 90 - af;
2526                        } else if ((af > 180) && (af <= 360)) {
2527                                af = 450 - af;
2528                        } else if ((af < -180) && (af >= -360)) {
2529                                af = -270 - af;
2530                        }
2531
2532                        LatLonPointImpl lp = Bearing.findPoint(lat0, lon0, af, r, null);
2533
2534                        EarthLocation el = new EarthLocationLite(lp.getLatitude(), lp
2535                                        .getLongitude(), 0);
2536                        StormTrackPoint sp = new StormTrackPoint(el, dt, 0, null);
2537
2538                        track[i] = sp;
2539                }
2540
2541                return track;
2542        }
2543
2544        /**
2545         * _more_
2546         * 
2547         * @param c
2548         *            _more_
2549         * @param angle
2550         *            _more_
2551         * @param r
2552         *            _more_
2553         * @param dt
2554         *            _more_
2555         * 
2556         * @return _more_
2557         * 
2558         * @throws VisADException
2559         *             _more_
2560         */
2561        public StormTrackPoint[] getHalfCircleTrackPointOld(EarthLocation c,
2562                        double angle, double r, DateTime dt) throws VisADException {
2563                // return 10 track point
2564                int size = 11;
2565
2566                StormTrackPoint[] track = new StormTrackPoint[size];
2567                FlatEarth e = new FlatEarth();
2568                ProjectionPoint p0 = e.latLonToProj(c.getLatitude().getValue(), c
2569                                .getLongitude().getValue());
2570
2571                for (int i = 0; i < size; i++) {
2572                        double af = angle + i * 15 * Math.PI / 180.0;
2573                        double x = p0.getX() + r * Math.cos(af);
2574                        double y = p0.getY() + r * Math.sin(af);
2575
2576                        ProjectionPoint pp = new ProjectionPointImpl(x, y);
2577                        LatLonPointImpl lp = new LatLonPointImpl();
2578                        FlatEarth e3 = new FlatEarth();
2579                        LatLonPoint lp11 = e3.projToLatLon(pp, lp);
2580                        EarthLocation el = new EarthLocationLite(lp11.getLatitude(), lp11
2581                                        .getLongitude(), 0);
2582                        StormTrackPoint sp = new StormTrackPoint(el, dt, 0, null);
2583
2584                        track[i] = sp;
2585                }
2586
2587                return track;
2588        }
2589
2590}