001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
005     * Space Science and Engineering Center (SSEC)
006     * University of Wisconsin - Madison
007     * 1225 W. Dayton Street, Madison, WI 53706, USA
008     * https://www.ssec.wisc.edu/mcidas
009     * 
010     * All Rights Reserved
011     * 
012     * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013     * some McIDAS-V source code is based on IDV and VisAD source code.  
014     * 
015     * McIDAS-V is free software; you can redistribute it and/or modify
016     * it under the terms of the GNU Lesser Public License as published by
017     * the Free Software Foundation; either version 3 of the License, or
018     * (at your option) any later version.
019     * 
020     * McIDAS-V is distributed in the hope that it will be useful,
021     * but WITHOUT ANY WARRANTY; without even the implied warranty of
022     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023     * GNU Lesser Public License for more details.
024     * 
025     * You should have received a copy of the GNU Lesser Public License
026     * along with this program.  If not, see http://www.gnu.org/licenses.
027     */
028    
029    package edu.wisc.ssec.mcidasv.control;
030    
031    import edu.wisc.ssec.mcidasv.McIdasPreferenceManager;
032    
033    import edu.wisc.ssec.mcidasv.data.GroundStations;
034    import edu.wisc.ssec.mcidasv.data.PolarOrbitTrackDataSource;
035    import edu.wisc.ssec.mcidasv.data.adde.sgp4.AstroConst;
036    import edu.wisc.ssec.mcidasv.data.hydra.CurveDrawer;
037    import edu.wisc.ssec.mcidasv.util.XmlUtil;
038    
039    import java.awt.BorderLayout;
040    import java.awt.Color;
041    import java.awt.Container;
042    import java.awt.FlowLayout;
043    import java.awt.event.ActionEvent;
044    import java.awt.event.ActionListener;
045    import java.awt.event.KeyEvent;
046    import java.awt.event.KeyListener;
047    import java.lang.Math;
048    import java.rmi.RemoteException;
049    import java.util.ArrayList;
050    import java.util.HashMap;
051    import java.util.List;
052    import java.util.TreeSet;
053    
054    import javax.swing.Box;
055    import javax.swing.BoxLayout;
056    import javax.swing.JButton;
057    import javax.swing.JComboBox;
058    import javax.swing.JComponent;
059    import javax.swing.JLabel;
060    import javax.swing.JOptionPane;
061    import javax.swing.JPanel;
062    import javax.swing.JSlider;
063    import javax.swing.JTextField;
064    
065    import org.slf4j.Logger;
066    import org.slf4j.LoggerFactory;
067    import org.w3c.dom.Element;
068    import org.w3c.dom.NodeList;
069    
070    import ucar.unidata.data.DataChoice;
071    import ucar.unidata.data.DataSourceImpl;
072    import ucar.unidata.idv.control.DisplayControlImpl;
073    import ucar.unidata.util.GuiUtils;
074    import ucar.unidata.util.GuiUtils.ColorSwatch;
075    import ucar.unidata.util.IOUtil;
076    import ucar.unidata.view.geoloc.NavigatedDisplay;
077    import ucar.visad.display.CompositeDisplayable;
078    import ucar.visad.display.Displayable;
079    import ucar.visad.display.TextDisplayable;
080    
081    import visad.Data;
082    import visad.DisplayRealType;
083    import visad.Gridded2DSet;
084    import visad.MathType;
085    import visad.RealTuple;
086    import visad.RealTupleType;
087    import visad.SampledSet;
088    import visad.Text;
089    import visad.TextControl;
090    import visad.TextType;
091    import visad.Tuple;
092    import visad.TupleType;
093    import visad.UnionSet;
094    import visad.VisADException;
095    import visad.georef.EarthLocation;
096    import visad.georef.EarthLocationTuple;
097    import visad.georef.LatLonPoint;
098    import visad.georef.LatLonTuple;
099    
100    /**
101     * {@link ucar.unidata.idv.control.PolarOrbitTrackControl} with some McIDAS-V
102     * specific extensions. Namely parameter sets and support for inverted 
103     * parameter defaults.
104     */
105    
106    public class PolarOrbitTrackControl extends DisplayControlImpl {
107    
108        private static final Logger logger = LoggerFactory.getLogger(PolarOrbitTrackControl.class);
109    
110        private JLabel satelliteName = new JLabel("");
111        private static final JLabel kmLabel = new JLabel("km");
112        private JTextField swathWidthFld = new JTextField(" ", 5);
113        private JPanel swathWidthPanel;
114        private JButton saveBtn;
115        
116        // Ground Station hashmap
117        private HashMap<String, EarthLocationTuple> stationMap = null;
118    
119        private double latitude;
120        private double longitude;
121        private JPanel fontSizePanel;
122        private JPanel colorPanel;
123        private JPanel antColorPanel;
124        private JPanel locationPanel;
125        private JPanel latLonAltPanel;
126    
127        /** Property name to get the list or urls */
128        public final String PREF_GROUNDSTATIONS = "mcv.groundstations";
129    
130        private JComboBox locationComboBox;
131        private JTextField locationEditor;
132    
133        private String station = "";
134        private TextDisplayable groundStationDsp;
135    
136        private static final int DEFAULT_ANTENNA_ANGLE = 5;
137        private static final int MAX_ANTENNA_ANGLE = 90;
138        private int angle = DEFAULT_ANTENNA_ANGLE;
139    
140        private DataChoice dataChoice;
141    
142        private JLabel latLabel;
143        private JLabel lonLabel;
144        private JLabel altLabel;
145        private JTextField antennaAngle = new JTextField("5", 5);
146    
147        private ActionListener fontSizeChange;
148    
149        /** Font size control */
150        private static final int SLIDER_MAX = 10;
151        private static final int SLIDER_MIN = 1;
152        
153        /** Ground station line width control */
154        private static final int GS_SLIDER_MAX = 4;
155        private static final int GS_SLIDER_MIN = 1;
156    
157        private JSlider fontSizeSlider;
158        private JSlider gsSizeSlider;
159        private JTextField fontSizeFld = new JTextField();
160    
161        private CompositeDisplayable trackDsp;
162        private CompositeDisplayable swathDsp;
163        private CompositeDisplayable circleDsp;
164        private TupleType tupleType;
165    
166        private int fontSize;
167        private int defaultSize = 3;
168        private ColorSwatch colorSwatch;
169        private Color color;
170        private Color defaultColor = Color.GREEN;
171        private ColorSwatch antColorSwatch;
172        private Color antColor;
173        private Color defaultAntColor = Color.MAGENTA;
174        private PolarOrbitTrackDataSource dataSource;
175    
176        private CurveDrawer coverageCircle;
177        private double satelliteAltitude = 0.0;
178    
179        private double centerAlt = 0.0;
180        private double centerLat = 0.0;
181        private double centerLon = 0.0;
182        private double satZ = 0.0;
183        private NavigatedDisplay navDsp = null;
184        private TextType textType = null;
185        private double width = 0.0;
186    
187        /** Path to the McV swathwidths.xml */
188        private static final String SWATH_WIDTHS = "/edu/wisc/ssec/mcidasv/resources/swathwidths.xml";
189        private static final String TAG_SATELLITE = "satellite";
190        private static final String ATTR_NAME = "name";
191        private static final String ATTR_WIDTH = "width";
192        private Element root = null;
193    
194        public PolarOrbitTrackControl() {
195            super();
196            logger.trace("created new tlecontrol={}", Integer.toHexString(hashCode()));
197            setAttributeFlags(FLAG_COLORTABLE);
198            try {
199                final String xml =
200                    IOUtil.readContents(SWATH_WIDTHS, McIdasPreferenceManager.class);
201                root = XmlUtil.getRoot(xml);
202            } catch (Exception e) {
203                System.out.println("problem reading swathwidths.xml e=" + e);
204            }
205        }
206    
207        @Override public boolean init(DataChoice dataChoice) 
208            throws VisADException, RemoteException 
209        {
210            // instantiate labels
211            latLabel = new JLabel();
212            lonLabel = new JLabel();
213            altLabel = new JLabel();
214            this.dataChoice = dataChoice;
215            String choiceName = dataChoice.getName();
216            NodeList nodeList = root.getElementsByTagName(TAG_SATELLITE);
217            int num = nodeList.getLength();
218            if (num > 0) {
219                for (int i = 0; i < num; i++) {
220                    Element n = (Element) (nodeList.item(i));
221                    String satName = n.getAttribute(ATTR_NAME);
222                    if (satName.equals(choiceName)) {
223                        String strWidth = n.getAttribute(ATTR_WIDTH);
224                        if (strWidth.isEmpty()) strWidth = "0";
225                        Double dWidth = new Double(strWidth);
226                        width = dWidth.doubleValue();
227                        break;
228                    }
229                }
230            }
231            try {
232                trackDsp = new CompositeDisplayable();
233                swathDsp = new CompositeDisplayable();
234                circleDsp = new CompositeDisplayable();
235            } catch (Exception e) {
236                System.out.println("problem creating composite displayable e=" + e);
237                return false;
238            }
239            boolean result = super.init((DataChoice)this.getDataChoices().get(0));
240    
241            tupleType = makeTupleType();
242    
243            Data data = getData(getDataInstance());
244            createTrackDisplay(data, true);
245            dataSource = getDataSource();
246            try {
247                navDsp = getNavigatedDisplay();
248                EarthLocation earthLoc = navDsp.getCenterPoint();
249                LatLonPoint llp = earthLoc.getLatLonPoint();
250                centerLat = llp.getLatitude().getValue();
251                centerLon = llp.getLongitude().getValue();
252                centerAlt = dataSource.getNearestAltToGroundStation(centerLat, centerLon) / 1000.0;
253                EarthLocationTuple elt = new EarthLocationTuple(centerLat, centerLon, centerAlt);
254                double[] xyz = navDsp.getSpatialCoordinates((EarthLocation) elt).getValues();
255                satZ = xyz[2] / 5.0;
256                applyTrackPosition();
257            } catch (Exception e) {
258                System.out.println("get display center e=" + e);
259            }
260    
261            return result;
262        }
263    
264        private void createTrackDisplay(Data data, boolean doTrack) {
265            try {
266                fontSize = getFontSize();
267                color = getColor();
268                List<String> dts = new ArrayList<String>();
269                if (data instanceof Tuple) {
270                    Data[] dataArr = ((Tuple) data).getComponents();
271    
272                    int npts = dataArr.length;
273                    float[][] latlon = new float[2][npts];
274    
275                    for (int i = 0; i < npts; i++) {
276                        Tuple t = (Tuple) dataArr[i];
277                        Data[] tupleComps = t.getComponents();
278    
279                        LatLonTuple llt = (LatLonTuple)tupleComps[1];
280                        double dlat = llt.getLatitude().getValue();
281                        double dlon = llt.getLongitude().getValue();
282    
283                        if (doTrack) {
284                            String str = ((Text)tupleComps[0]).getValue();
285                            dts.add(str);
286                            int indx = str.indexOf(" ") + 1;
287                            String subStr = "- " + str.substring(indx, indx+5);
288                            TextDisplayable time = new TextDisplayable(getTextType());
289                            time.setJustification(TextControl.Justification.LEFT);
290                            time.setVerticalJustification(TextControl.Justification.CENTER);
291                            time.setColor(color);
292     
293                            RealTuple lonLat =
294                                new RealTuple(RealTupleType.SpatialEarth2DTuple,
295                                    new double[] { dlon, dlat });
296                            if ((i % 5) == 0) {
297                                Tuple tup = new Tuple(getTupleType(),
298                                    new Data[] { lonLat, new Text(getTextType(), subStr)});
299                                time.setData(tup);
300                                trackDsp.addDisplayable(time);
301                            }
302                        }
303                        float lat = (float) dlat;
304                        float lon = (float) dlon;
305                        latlon[0][i] = lat;
306                        latlon[1][i] = lon;
307                    }
308    
309                    if (doTrack) {
310                        setDisplayableTextSize(fontSize);
311                        Gridded2DSet track = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
312                                   latlon, npts);
313                        SampledSet[] set = new SampledSet[1];
314                        set[0] = track;
315                        UnionSet uset = new UnionSet(set);
316                        CurveDrawer trackLines = new CurveDrawer(uset);
317                        trackLines.setData(uset);
318                        trackDsp.addDisplayable(trackLines);
319                        trackLines.setDrawingEnabled(false);
320    
321                        trackDsp.setColor(color);
322                        trackDsp.setLineWidth(2.0f);
323    
324                        addDisplayable(trackDsp, FLAG_COLORTABLE);
325                    }
326    
327                    float[][][] crv = getSwath(latlon);
328                    int npt = crv[0][0].length;
329                    float[][] leftC = new float[2][npt];
330                    float[][] rightC = new float[2][npt];
331                    for (int i=0; i<npt; i++) {
332                        leftC[0][i] = crv[0][0][i];
333                        leftC[1][i] = crv[0][1][i];
334                        rightC[0][i] = crv[1][0][i];
335                        rightC[1][i] = crv[1][1][i];
336                    }
337                    Gridded2DSet left = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
338                               leftC, npt);
339                    SampledSet[] lSet = new SampledSet[1];
340                    lSet[0] = left;
341                    UnionSet lUSet = new UnionSet(lSet);
342                    CurveDrawer leftLines = new CurveDrawer(lUSet);
343                    leftLines.setLineStyle(1);
344                    leftLines.setData(lUSet);
345                    swathDsp.addDisplayable(leftLines);
346                    leftLines.setDrawingEnabled(false);
347    
348                    Gridded2DSet right = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
349                               rightC, npt);
350                    SampledSet[] rSet = new SampledSet[1];
351                    rSet[0] = right;
352                    UnionSet rUSet = new UnionSet(rSet);
353                    CurveDrawer rightLines = new CurveDrawer(rUSet);
354                    rightLines.setLineStyle(1);
355                    rightLines.setData(rUSet);
356                    swathDsp.addDisplayable(rightLines);
357                    rightLines.setDrawingEnabled(false);
358    
359                    swathDsp.setColor(color);
360                    swathDsp.setLineWidth(1.0f);
361                    addDisplayable(swathDsp, FLAG_COLORTABLE);
362                }
363            } catch (Exception e) {
364                System.out.println("getData e=" + e);
365            }
366            return;
367        }
368    
369        private float[][][] getSwath(float[][] track) {
370            double earthRadius = AstroConst.R_Earth_mean / 1000.0;
371            int npt = track[0].length-1;
372            float[][][] ret = new float[2][2][npt-1];
373            try {
374                int indx = 0;
375                for (int i = 1; i < npt; i++) {
376                    double latA = Math.toRadians(track[0][i-1]);
377                    double lonA = Math.toRadians(track[1][i-1]);
378    
379                    double latB = Math.toRadians(track[0][i+1]);
380                    double lonB = Math.toRadians(track[1][i+1]);
381    
382                    double diffLon = lonB - lonA;
383                    double bX = Math.cos(latB) * Math.cos(diffLon);
384                    double bY = Math.cos(latB) * Math.sin(diffLon);
385                    double xFac = Math.cos(latA) + bX;
386                    double latC = Math.atan2(Math.sin(latA) + Math.sin(latB), Math.sqrt(xFac * xFac + bY * bY));
387                    double lonC = lonA + Math.atan2(bY, xFac);
388    
389                    double bearing = Math.atan2(Math.sin(diffLon) * Math.cos(latB),
390                                     Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(diffLon))
391                                     + Math.PI / 2.0;
392                    double dist = width / 2.0;
393                    dist /= earthRadius;
394                    double lat = Math.asin(Math.sin(latC) * Math.cos(dist) +
395                                           Math.cos(latC) * Math.sin(dist) * Math.cos(bearing));
396                    double lon = lonC + Math.atan2(Math.sin(bearing) * Math.sin(dist) * Math.cos(latC),
397                                                   Math.cos(dist) - Math.sin(latC) * Math.sin(lat));
398                    float latD = (float)Math.toDegrees(lat);
399                    float lonD = (float)Math.toDegrees(lon);
400    
401                    bearing += Math.PI;
402                    lat = Math.asin(Math.sin(latC) * Math.cos(dist) +
403                                           Math.cos(latC) * Math.sin(dist) * Math.cos(bearing));
404                    lon = lonC + Math.atan2(Math.sin(bearing) * Math.sin(dist) * Math.cos(latC),
405                                                   Math.cos(dist) - Math.sin(latC) * Math.sin(lat));
406                    float latE = (float) Math.toDegrees(lat);
407                    float lonE = (float) Math.toDegrees(lon);
408    
409                    ret[0][0][indx] = latD;
410                    ret[0][1][indx] = lonD;
411    
412                    ret[1][0][indx] = latE;
413                    ret[1][1][indx] = lonE;
414                    ++indx;
415                }
416            } catch (Exception e) {
417                System.out.println("e=" + e);
418                return null;
419            }
420            return ret;
421        }
422    
423        private TupleType makeTupleType() {
424            TupleType t = null;
425            try {
426                t = new TupleType(new MathType[] {RealTupleType.SpatialEarth2DTuple,
427                                                  getTextType()});
428            } catch (Exception e) {
429                System.out.println("\nPolarOrbitTrackControl.makeTupleType e=" + e);
430                System.out.println("    textType=" + getTextType());
431            }
432            return t;
433        }
434    
435        public JComponent makeColorBox(Color swatchColor) {
436            GuiUtils.ColorSwatch swatch = new GuiUtils.ColorSwatch(swatchColor, "Color") {
437                public void userSelectedNewColor(Color c) {
438                    try {
439                        getIdv().showWaitCursor();
440                        setColor(c);
441                        setBackground(c);
442                        getIdv().showNormalCursor();
443                    } catch (Exception e) {
444                        System.out.println("\nsetColor e=" + e);
445                        setColor(defaultColor);
446                    }
447                }
448            };
449            return swatch;
450        }
451    
452        public JComponent makeAntColorBox(Color swatchAntColor) {
453            GuiUtils.ColorSwatch swatch = new GuiUtils.ColorSwatch(swatchAntColor, "Color") {
454                public void userSelectedNewColor(Color c) {
455                    try {
456                        getIdv().showWaitCursor();
457                        setAntColor(c);
458                        setBackground(c);
459                        getIdv().showNormalCursor();
460                    } catch (Exception e) {
461                        System.out.println("\nsetAntColor e=" + e);
462                        setAntColor(defaultAntColor);
463                    }
464                }
465            };
466            return swatch;
467        }
468    
469        /**
470         * Called by doMakeWindow in DisplayControlImpl, which then calls its
471         * doMakeMainButtonPanel(), which makes more buttons.
472         *
473         * @return container of contents
474         */
475        
476        public Container doMakeContents() {
477            fontSizeChange =new ActionListener() {
478                public void actionPerformed(ActionEvent ae) {
479                    String str = fontSizeFld.getText();
480                    int size = new Integer(str).intValue();
481                    moveFontSizeSlider(size);
482                    setDisplayableTextSize(size);
483                }
484            };
485    
486            fontSizeSlider = GuiUtils.makeSlider(SLIDER_MIN, SLIDER_MAX, defaultSize,
487                                         this, "sliderChanged", true);
488            fontSizeSlider.setMajorTickSpacing(1);
489            fontSizeSlider.setSnapToTicks(true);
490            fontSizeSlider.setPaintTicks(true);
491            fontSizeSlider.setPaintLabels(true);
492            int size = fontSizeSlider.getValue();
493            setFontSize(size);
494            fontSizeFld = new JTextField(Integer.toString(size), 3);
495            fontSizeFld.addActionListener(fontSizeChange);
496            
497            // init the ground station line width control slider
498            gsSizeSlider = new JSlider();
499            gsSizeSlider = GuiUtils.makeSlider(GS_SLIDER_MIN, GS_SLIDER_MAX, defaultSize,
500                    this, "gsSliderChanged", true);
501            gsSizeSlider.setMajorTickSpacing(1);
502            gsSizeSlider.setSnapToTicks(true);
503            gsSizeSlider.setPaintTicks(true);
504            gsSizeSlider.setPaintLabels(true);
505            gsSizeSlider.setValue(GS_SLIDER_MIN);
506            
507            fontSizePanel = new JPanel();
508            fontSizePanel.setLayout(new FlowLayout(FlowLayout.LEFT));
509            fontSizePanel.add(new JLabel("Font Size: "));
510            fontSizePanel.add(fontSizeFld);
511            fontSizePanel.add(fontSizeSlider);
512    
513            Color swatchColor = getColor();
514            colorSwatch = (GuiUtils.ColorSwatch)makeColorBox(swatchColor);
515            
516            colorPanel = new JPanel();
517            colorPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
518            colorPanel.add(new JLabel("Set Color: "));
519            colorPanel.add(colorSwatch);
520            
521            JPanel groundStationPanel = makeGroundStationPanel();
522    
523            swathWidthPanel = makeSwathWidthPanel();
524            
525            JPanel outerPanel = new JPanel(new BorderLayout());
526            
527            JPanel mainPanel = new JPanel();
528            mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
529            mainPanel.add(swathWidthPanel);
530            mainPanel.add(fontSizePanel);
531            mainPanel.add(colorPanel);
532            mainPanel.add(locationPanel);
533            mainPanel.add(groundStationPanel);
534            mainPanel.add(latLonAltPanel);
535            mainPanel.add(antColorPanel);
536            
537            outerPanel.add(mainPanel, BorderLayout.NORTH);
538            
539            return outerPanel;
540        }
541    
542        private JPanel makeGroundStationPanel() {
543            locationComboBox = new JComboBox();
544            locationComboBox.setEditable(true);
545            locationEditor = (JTextField) locationComboBox.getEditor().getEditorComponent();
546            locationEditor.addKeyListener(new KeyListener() {
547                public void keyPressed(KeyEvent e) {}
548                public void keyReleased(KeyEvent e) {}
549                            @Override
550                            public void keyTyped(KeyEvent arg0) {
551                                    // TODO Auto-generated method stub
552                            }
553            });
554    
555            // Ground Stations are now a natural-order map (alphabetical)
556            GroundStations gs = new GroundStations(null);
557            stationMap = gs.getGroundStations();
558            TreeSet<String> keySet = new TreeSet<String>(stationMap.keySet());
559    
560            GuiUtils.setListData(locationComboBox, keySet.toArray());
561    
562            locationComboBox.addActionListener(new ActionListener() {
563                public void actionPerformed(ActionEvent ae) {
564                    setStation((String) locationComboBox.getSelectedItem());
565                    try {
566                            EarthLocationTuple elt = stationMap.get(station);
567                        latLabel.setText(elt.getLatitude().toString());
568                        lonLabel.setText(elt.getLongitude().toString());
569                            altLabel.setText(elt.getAltitude().toString());
570                            setLatitude();
571                            setLongitude();
572                        int val = getAntennaAngle();
573                        setAntennaAngle(val);
574                    } catch (Exception e) {
575                    }
576                    setSatelliteAltitude(dataSource.getNearestAltToGroundStation(latitude, longitude) / 1000.0);
577                    redrawCoverageCircle();
578                }
579            });
580    
581            // initialize with first Earth Location in our map
582            EarthLocationTuple elt = stationMap.get(locationComboBox.getSelectedItem());
583    
584            latLabel.setText(elt.getLatitude().toString());
585            lonLabel.setText(elt.getLongitude().toString());
586            altLabel.setText(elt.getAltitude().toString());
587    
588            setLatitude();
589            setLongitude();
590    
591            antennaAngle.addActionListener(new ActionListener() {
592                public void actionPerformed(ActionEvent ae) {
593                    String str = antennaAngle.getText();
594                    Integer iVal = new Integer(str.trim());
595                    int val = iVal.intValue();
596                    setAntennaAngle(val);
597                    redrawCoverageCircle();
598                }
599            });
600            
601            locationPanel = new JPanel();
602            locationPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
603            locationPanel.add(new JLabel("Ground Station:"));
604            locationPanel.add(locationComboBox);
605            
606            latLonAltPanel = new JPanel();
607            latLonAltPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
608            
609            latLonAltPanel.add(new JLabel("Latitude: "));
610            latLonAltPanel.add(latLabel);
611            latLonAltPanel.add(Box.createHorizontalStrut(5));
612            
613            latLonAltPanel.add(new JLabel("Longitude: "));
614            latLonAltPanel.add(lonLabel);
615            latLonAltPanel.add(Box.createHorizontalStrut(5));
616            
617            latLonAltPanel.add(new JLabel("Altitude: "));
618            latLonAltPanel.add(altLabel);
619            latLonAltPanel.add(Box.createHorizontalStrut(5));
620            latLonAltPanel.add(new JLabel("Antenna Angle: "));
621            latLonAltPanel.add(antennaAngle);
622    
623            Color swatchAntColor = getAntColor();
624            antColorSwatch = (GuiUtils.ColorSwatch)makeAntColorBox(swatchAntColor);
625            
626            antColorPanel = new JPanel();
627            antColorPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
628            antColorPanel.add(new JLabel("Set Ground Station Color: "));
629            antColorPanel.add(antColorSwatch);
630            
631            antColorPanel.add(Box.createHorizontalStrut(5));
632            antColorPanel.add(new JLabel("Set Ground Station Line Width: "));
633            antColorPanel.add(gsSizeSlider);
634            
635            return latLonAltPanel;
636        }
637    
638        private JPanel makeSwathWidthPanel() {
639            if (dataChoice != null)
640                satelliteName = new JLabel(dataChoice.getName());
641            Double dWidth = new Double(width);
642            swathWidthFld = new JTextField(dWidth.toString(), 6);
643            swathWidthFld.addActionListener(new ActionListener() {
644                public void actionPerformed(ActionEvent ae) {
645                    changeSwathWidth();
646                }
647            });
648    
649            saveBtn = new JButton("Save");
650    
651            JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT));
652            jp.add(new JLabel("Satellite: "));
653            jp.add(satelliteName);
654            jp.add(Box.createHorizontalStrut(5));
655            jp.add(new JLabel("Swath Width: "));
656            jp.add(swathWidthFld);
657            jp.add(kmLabel);
658            jp.add(Box.createHorizontalStrut(5));
659            jp.add(saveBtn);
660            
661            return jp;
662        }
663    
664        private void changeSwathWidth() {
665            String str = swathWidthFld.getText();
666            Double dVal = new Double(str.trim());
667            double val = dVal.doubleValue();
668            setSwathWidth(val);
669            try {
670                removeDisplayable(swathDsp);
671                Data data = getData(getDataInstance());
672                swathDsp = new CompositeDisplayable();
673                createTrackDisplay(data, false);
674            } catch (Exception e) {
675                System.out.println("\nproblem redrawing swaths e=" + e);
676            }
677        }
678    
679        private void setSwathWidth(double val) {
680            width = val;
681        }
682    
683        /**
684         * Apply the map (height) position to the displays
685         */
686        
687        private void applyTrackPosition() {
688            try {
689                DisplayRealType dispType = navDsp.getDisplayAltitudeType();
690                trackDsp.setConstantPosition(satZ, dispType);
691            } catch (Exception exc) {
692                System.out.println("Setting track z-position exc=" + exc);
693            }
694        }
695    
696        private void redrawCoverageCircle() {
697            try {
698    
699                int num = circleDsp.displayableCount();
700                for (int i = 0; i < num; i++) {
701                    circleDsp.removeDisplayable(0);
702                }
703    
704                if (drawCoverageCircle(Math.toRadians(latitude), Math.toRadians(longitude),
705                           satelliteAltitude, getAntColor()) != null) {
706                    drawGroundStation();
707                    circleDsp.setColor(getAntColor());
708                    circleDsp.addDisplayable(coverageCircle);
709                    circleDsp.addDisplayable(groundStationDsp);
710                    addDisplayable(circleDsp, FLAG_COLORTABLE);
711                }
712            } catch (Exception e) {
713                System.out.println("redrawCoverageCircle e=" + e);
714            }
715        }
716    
717        private CurveDrawer drawCoverageCircle(double lat, double lon, double satAlt, Color color) {
718    
719            /* mean earthRadius in km */
720            double earthRadius = AstroConst.R_Earth_mean / 1000.0;
721            satAlt += earthRadius;
722            double pi = Math.PI;
723            double SAC = pi / 2.0 + Math.toRadians(getAntennaAngle());
724            double sinASC = earthRadius * Math.sin(SAC) / satAlt;
725            double dist = earthRadius * (Math.PI - SAC - Math.asin(sinASC));
726            double rat = dist / earthRadius;
727    
728            int npts = 360;
729            float[][] latlon = new float[2][npts];
730            double cosDist = Math.cos(rat);
731            double sinDist = Math.sin(rat);
732            double sinLat = Math.sin(lat);
733            double cosLat = Math.cos(lat);
734            double sinLon = -Math.sin(lon);
735            double cosLon = Math.cos(lon);
736            for (int i = 0; i < npts; i++) {
737                double azimuth = Math.toRadians((double)i);
738                double cosBear = Math.cos(azimuth);
739                double sinBear = Math.sin(azimuth);
740                double z = cosDist * sinLat +
741                           sinDist * cosLat * cosBear;
742                double y = cosLat * cosLon * cosDist +
743                           sinDist * (sinLon * sinBear - sinLat * cosLon * cosBear);
744                double x = cosLat * sinLon * cosDist -
745                           sinDist * (cosLon * sinBear + sinLat * sinLon * cosBear);
746                double r = Math.sqrt(x*x + y*y);
747                double latRad = Math.atan2(z, r);
748                double lonRad = 0.0;
749                if (r > 0.0) lonRad = -Math.atan2(x, y);
750                latlon[0][i] = (float) Math.toDegrees(latRad);
751                latlon[1][i] = (float) Math.toDegrees(lonRad);
752            }
753            try {
754                Gridded2DSet circle = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
755                                   latlon, npts);
756                SampledSet[] set = new SampledSet[1];
757                set[0] = circle;
758                UnionSet uset = new UnionSet(set);
759                coverageCircle = new CurveDrawer(uset);
760                coverageCircle.setLineStyle(1);
761                coverageCircle.setColor(getAntColor());
762                coverageCircle.setData(uset);
763                coverageCircle.setDrawingEnabled(false);
764            } catch (Exception e) {
765                System.out.println("drawCoverageCircle e=" + e);
766                return null;
767            }
768            return coverageCircle;
769        }
770    
771        public int getFontSize() {
772            if (fontSize < 1) fontSize = defaultSize;
773            return fontSize;
774        }
775    
776        public void setFontSizeTextField(int size) {
777            size = setFontSize(size);
778            try {
779                if (fontSizeFld != null) {
780                    fontSizeFld.setText(new Integer(size).toString());
781                }
782            } catch (Exception e) {
783                System.out.println("Exception in PolarOrbitTrackControl.setFontSizeTextField e=" + e);
784            }
785        }
786    
787        private void moveFontSizeSlider(int size) {
788            size = setFontSize(size);
789            try {
790                if (fontSizeSlider != null) {
791                    fontSizeSlider.setValue(size);
792                }
793            } catch (Exception e) {
794                System.out.println("Exception in PolarOrbitTrackControl.moveFontSizeSlider e=" + e);
795            }
796        }
797    
798        private void setDisplayableTextSize(int size) {
799            size = setFontSize(size);
800            try {
801                float fSize = (float) size / 10.0f;
802                int num = trackDsp.displayableCount() - 1;
803                TextDisplayable textDsp = null;
804                for (int i = num; i > -1; i--) {
805                    Displayable dsp = trackDsp.getDisplayable(i);
806                    if (dsp instanceof TextDisplayable) {
807                        textDsp = (TextDisplayable)dsp;
808                        break;
809                    }
810                }
811                if (textDsp != null) {
812                    textDsp.setTextSize(fSize);
813                }
814            } catch (Exception e) {
815                System.out.println("Exception in PolarOrbitTrackControl.setDisplayableTextSize e=" + e);
816            }
817        }
818    
819        public int setFontSize(int size) {
820            if (size < 1) size = defaultSize;
821            fontSize = size;
822            return fontSize;
823        }
824    
825        public Color getColor() {
826            if (color == null) color = defaultColor;
827            return color;
828        }
829    
830        public void setColor(Color c) {
831            if (c == null) c = defaultColor;
832            try {
833                trackDsp.setColor(c);
834                swathDsp.setColor(c);
835                color = c;
836            } catch (Exception e) {
837                System.out.println("Exception in PolarOrbitTrackControl.setColor e=" + e);
838            }
839        }
840    
841        public Color getAntColor() {
842            if (antColor == null) antColor = defaultAntColor;
843            return antColor;
844        }
845    
846        public void setAntColor(Color c) {
847            if (c == null) c = defaultAntColor;
848            try {
849                antColor = c;
850                circleDsp.setColor(c);
851            } catch (Exception e) {
852                System.out.println("Exception in PolarOrbitTrackControl.setAntColor e=" + e);
853            }
854        }
855    
856        public void setLatitude() {
857            latitude = Double.parseDouble(latLabel.getText());
858        }
859    
860        public double getLatitude() {
861            return latitude;
862        }
863    
864        public void setLongitude() {
865            longitude = Double.parseDouble(lonLabel.getText());
866        }
867    
868        public double getLongitude() {
869            return longitude;
870        }
871    
872        public void sliderChanged(int sliderValue) {
873            setFontSizeTextField(sliderValue);
874            setDisplayableTextSize(sliderValue);
875        }
876        
877        public void gsSliderChanged(int sliderValue) {
878            // ground station outline width
879            try {
880                            circleDsp.setLineWidth(sliderValue);
881                    // doubtful these could ever happen, but required       
882                    } catch (RemoteException e) {
883                            e.printStackTrace();
884                    } catch (VisADException e) {
885                            e.printStackTrace();
886                    }
887        }
888    
889        public void setStation(String val) {
890            station = val.trim();
891        }
892    
893        public String getStation() {
894            return station;
895        }
896    
897        public void setAntennaAngle(int val) {
898            if ((val < DEFAULT_ANTENNA_ANGLE) || (val > MAX_ANTENNA_ANGLE)) {
899                    // throw up a dialog to tell user the problem
900                    JOptionPane.showMessageDialog(latLonAltPanel, 
901                                    "Antenna angle valid range is " + DEFAULT_ANTENNA_ANGLE + 
902                                    " to " + MAX_ANTENNA_ANGLE + " degrees");
903            } else {
904                    String str = " " + val;
905                    antennaAngle.setText(str);
906                    angle = val;
907            }
908        }
909    
910        public int getAntennaAngle() {
911            String str = antennaAngle.getText();
912            angle = new Integer(str.trim()).intValue();
913            if (angle < DEFAULT_ANTENNA_ANGLE) angle = DEFAULT_ANTENNA_ANGLE;
914            return angle;
915        }
916    
917        private void setSatelliteAltitude(double val) {
918            satelliteAltitude = val;
919        }
920    
921        private void drawGroundStation() {
922            try {
923                String str = "+" + getStation();
924                groundStationDsp = new TextDisplayable(getTextType());
925                groundStationDsp.setJustification(TextControl.Justification.LEFT);
926                groundStationDsp.setVerticalJustification(TextControl.Justification.CENTER);
927                float tSize = (float) getFontSize() / 10.0f;
928                groundStationDsp.setTextSize(tSize);
929                groundStationDsp.setColor(getAntColor());
930                        
931                double dlat = getLatitude();
932                double dlon = getLongitude();
933                RealTuple lonLat =
934                    new RealTuple(RealTupleType.SpatialEarth2DTuple,
935                        new double[] { dlon, dlat });
936                Tuple tup = new Tuple(getTupleType(),
937                    new Data[] { lonLat, new Text(getTextType(), str)});
938                groundStationDsp.setData(tup);
939            } catch (Exception e) {
940                System.out.println("drawGroundStation e=" + e);
941            }
942        }
943            
944        public PolarOrbitTrackDataSource getDataSource() {
945            DataSourceImpl ds = null;
946            List dataSources = getDataSources();
947            boolean gotit = false;
948            if (!dataSources.isEmpty()) {
949                int nsrc = dataSources.size();
950                for (int i = 0; i < nsrc; i++) {
951                    ds = (DataSourceImpl) dataSources.get(nsrc-i-1);
952                    if (ds instanceof PolarOrbitTrackDataSource) {
953                        gotit = true;
954                        break;
955                    }
956                }
957            }
958            if (!gotit) return null;
959            return (PolarOrbitTrackDataSource) ds;
960        }
961    
962        private TextType getTextType() {
963            if (textType == null) {
964                String dispName = getDisplayName();
965                setDisplayName(getLongParamName() + " " + dispName);
966                try {
967                    String longName = getLongParamName().replaceAll(" ", "");
968                    textType = new TextType(longName);
969                } catch (Exception e) {
970                    textType = TextType.Generic;
971                }
972            }
973            return textType;
974        }
975    
976        private TupleType getTupleType() {
977            return tupleType;
978        }
979    }