001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2015
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;
030
031import edu.wisc.ssec.mcidasv.McIdasPreferenceManager;
032
033import edu.wisc.ssec.mcidasv.data.GroundStations;
034import edu.wisc.ssec.mcidasv.data.PolarOrbitTrackDataSource;
035import edu.wisc.ssec.mcidasv.data.TimeRangeSelection;
036import edu.wisc.ssec.mcidasv.data.hydra.CurveDrawer;
037import edu.wisc.ssec.mcidasv.ui.ColorSwatchComponent;
038import edu.wisc.ssec.mcidasv.util.XmlUtil;
039
040import java.awt.Color;
041import java.awt.Container;
042import java.awt.Dimension;
043import java.awt.FlowLayout;
044import java.awt.Font;
045import java.awt.event.ActionEvent;
046import java.awt.event.ItemEvent;
047import java.lang.Math;
048import java.rmi.RemoteException;
049import java.util.ArrayList;
050import java.util.HashMap;
051import java.util.List;
052import java.util.TreeSet;
053
054import javax.swing.BorderFactory;
055import javax.swing.Box;
056import javax.swing.BoxLayout;
057import javax.swing.JButton;
058import javax.swing.JCheckBox;
059import javax.swing.JComboBox;
060import javax.swing.JLabel;
061import javax.swing.JOptionPane;
062import javax.swing.JPanel;
063import javax.swing.JSpinner;
064import javax.swing.JTextField;
065import javax.swing.SpinnerNumberModel;
066
067import name.gano.astro.AstroConst;
068import net.miginfocom.swing.MigLayout;
069
070import org.slf4j.Logger;
071import org.slf4j.LoggerFactory;
072import org.w3c.dom.Element;
073import org.w3c.dom.NodeList;
074
075import ucar.unidata.data.DataChoice;
076import ucar.unidata.data.DataSourceImpl;
077import ucar.unidata.idv.control.DisplayControlImpl;
078import ucar.unidata.ui.FontSelector;
079import ucar.unidata.util.GuiUtils;
080import ucar.unidata.util.IOUtil;
081import ucar.unidata.view.geoloc.NavigatedDisplay;
082import ucar.visad.UtcDate;
083import ucar.visad.Util;
084import ucar.visad.display.CompositeDisplayable;
085import ucar.visad.display.TextDisplayable;
086
087import visad.Data;
088import visad.DisplayRealType;
089import visad.Gridded2DSet;
090import visad.MathType;
091import visad.RealTuple;
092import visad.RealTupleType;
093import visad.SampledSet;
094import visad.Text;
095import visad.TextControl;
096import visad.TextType;
097import visad.Tuple;
098import visad.TupleType;
099import visad.UnionSet;
100import visad.VisADException;
101import visad.georef.EarthLocationTuple;
102import visad.georef.LatLonTuple;
103
104/**
105 * {@link ucar.unidata.idv.control.DisplayControlImpl} with some McIDAS-V
106 * specific extensions. Namely parameter sets and support for inverted 
107 * parameter defaults.
108 */
109
110public class PolarOrbitTrackControl extends DisplayControlImpl {
111
112    private static final Logger logger = LoggerFactory.getLogger(PolarOrbitTrackControl.class);
113
114    private JLabel satelliteName = new JLabel("");
115    private static final JLabel kmLabel = new JLabel("km");
116    private JTextField swathWidthFld = null;
117    private JPanel swathWidthPanel;
118    
119    // Ground Station hashmap
120    private HashMap<String, EarthLocationTuple> stationMap = null;
121
122    private double latitude;
123    private double longitude;
124    private JPanel fontSizePanel;
125    private JPanel colorPanel;
126    private JPanel antColorPanel;
127    private JPanel locationPanel;
128    private JPanel latLonAltPanel;
129
130    /** Property name to get the list or URLs */
131    public final String PREF_GROUNDSTATIONS = "mcv.groundstations";
132
133    private JComboBox locationComboBox;
134    private JComboBox jcbStationsPlotted;
135    String [] lineStyles = new String[] { "_____", "_ _ _", ".....", "_._._" };
136    private JComboBox jcbTrackLineStyle = new JComboBox(lineStyles);
137    private JComboBox jcbEdgeLineStyle = new JComboBox(lineStyles);
138    private JComboBox jcbStationLineStyle = new JComboBox(lineStyles);
139    private JCheckBox jcbLabels;
140    private JCheckBox jcbSwathEdges;
141    
142    // names to distinguish checkbox event sources
143    private static final String CHECKBOX_LABELS = "CHECKBOX_LABELS";
144    private static final String CHECKBOX_SWATH_EDGES = "CHECKBOX_SWATH_EDGES";
145
146    private String station = "";
147
148    private static final int SWATH_WIDTH_MIN = 1;
149    // TJJ Feb 2014 - need to determine max of any sensor. VIIRS is over 3000 km
150    private static final int SWATH_WIDTH_MAX = 4000;
151    private static final int DEFAULT_ANTENNA_ANGLE = 5;
152    private static final int MAX_ANTENNA_ANGLE = 90;
153    private int curAngle = DEFAULT_ANTENNA_ANGLE;
154    private static final double LABEL_DISTANCE_THRESHOLD = 5.0d;
155
156    private DataChoice dataChoice;
157
158    private JLabel latLabel;
159    private JLabel lonLabel;
160    private JLabel altLabel;
161    private JTextField antennaAngle = new JTextField("" + DEFAULT_ANTENNA_ANGLE, DEFAULT_ANTENNA_ANGLE);
162    
163    // custom ground station UI components
164    JTextField customLat = null;
165    JTextField customLon = null;
166    JTextField customLab = null;
167
168    /** the font selectors, Orbit Track (ot) and Ground Station (gs) */
169    private FontSelector otFontSelector;
170    private Font otCurFont = FontSelector.DEFAULT_FONT;
171    private FontSelector gsFontSelector;
172    private Font gsCurFont = FontSelector.DEFAULT_FONT;
173    
174    // line width combo boxes, GS: Ground Station, SC: Swath Center, SE: Swath Edge
175    private JComboBox jcbGSLineWidth;
176    private JComboBox jcbSCLineWidth;
177    private JComboBox jcbSELineWidth;
178    private JSpinner js = null;
179
180    private CompositeDisplayable trackDsp;
181    private CompositeDisplayable timeLabelDsp;
182    private CompositeDisplayable stationLabelDsp;
183    private CompositeDisplayable swathEdgeDsp;
184    private CompositeDisplayable circleDsp;
185    
186    // time label variables
187    private static final int DEFAULT_LABEL_INTERVAL = 5;
188    private int labelInterval = DEFAULT_LABEL_INTERVAL;
189
190    private ColorSwatchComponent colorSwatch;
191
192    private static final Color DEFAULT_COLOR = Color.GREEN;
193    private Color curSwathColor = DEFAULT_COLOR;
194    private Color prvSwathColor = null;
195    
196    private ColorSwatchComponent antColorSwatch;
197    private Color antColor;
198    private Color defaultAntColor = Color.MAGENTA;
199    private PolarOrbitTrackDataSource dataSource;
200
201    private double satelliteAltitude = 0.0;
202
203    private double trackZ = 0.0d;
204    private double gsZ = 0.0d;
205    private NavigatedDisplay navDsp = null;
206    private TextType otTextType = null;
207    private TextType gsTextType = null;
208    private double curWidth = 0.0d;
209    private double prvWidth = 0.0d;
210    // TODO: event handler for ground station controls needs conditional handling checks
211    // e.g., utilize the unused variable below
212    private int prvStationLineStyle = -1;
213    private int prvTrackLineStyle = -1;
214    private int prvEdgeLineStyle = -1;
215    private int curTrackLineStyle = -1;
216    private int curEdgeLineStyle = -1;
217    private static final float FONT_SCALE_FACTOR = 12.0f;
218    
219    // line width for drawing track center and swath edges
220    private float prvSwathCenterWidth = 2.0f;
221    private float curSwathCenterWidth = 2.0f;
222    private float prvSwathEdgeWidth = 1.0f;
223    private float curSwathEdgeWidth = 1.0f;
224
225    /** Path to the McV swathwidths.xml */
226    private static final String SWATH_WIDTHS = "/edu/wisc/ssec/mcidasv/resources/swathwidths.xml";
227    private static final String TAG_SATELLITE = "satellite";
228    private static final String ATTR_NAME = "name";
229    private static final String ATTR_WIDTH = "width";
230
231        private static final String SWATH_MODS = "OrbitTrack";
232        private static final String STATION_MODS = "GroundStation";
233        private static final String STATION_ADD = "AddStation";
234        private static final String STATION_REM = "RemStation";
235        private static final String CUSTOM_ADD = "AddCustom";
236
237    private Element root = null;
238
239    public PolarOrbitTrackControl() {
240        super();
241        logger.trace("created new PolarOrbitTrackControl...");
242        setAttributeFlags(FLAG_COLORTABLE);
243        try {
244            final String xml =
245                IOUtil.readContents(SWATH_WIDTHS, McIdasPreferenceManager.class);
246            root = XmlUtil.getRoot(xml);
247        } catch (Exception e) {
248            logger.error("problem reading swathwidths.xml");
249            e.printStackTrace();
250        }
251    }
252
253    /**
254     * Deal with action events
255     *
256     * @param  ae the ActionEvent fired when the user applies changes
257     */
258
259    public void actionPerformed(ActionEvent ae) {
260        
261        // user trying to add a custom ground station
262        if (CUSTOM_ADD.equals(ae.getActionCommand())) {
263                logger.debug("Custom Ground Station...");
264                String labStr = customLab.getText();
265                if ((labStr == null) || (labStr.isEmpty())) {
266                JOptionPane.showMessageDialog(null, 
267                        "Please provide a label for the custom ground station.");
268                return;
269                }
270                float fLat;
271                float fLon;
272                try {
273                                fLat = Float.parseFloat(customLat.getText());
274                                fLon = Float.parseFloat(customLon.getText());
275                        } catch (NumberFormatException nfe) {
276                JOptionPane.showMessageDialog(null, 
277                        "Latitude and Longitude must be floating point numbers, please correct.");
278                return;
279                        }
280                if ((fLat < -90) || (fLat > 90)) {
281                JOptionPane.showMessageDialog(null, 
282                        "Latitude is out of valid range: " + fLat);
283                return;
284                }
285                if ((fLon < -180) || (fLon > 180)) {
286                JOptionPane.showMessageDialog(null, 
287                        "Longitude is out of valid range: " + fLon);
288                return;
289                }
290                // last check, is this label already used?
291                int numPlotted = jcbStationsPlotted.getItemCount();
292                for (int i = 0; i < numPlotted; i++) {
293                        String s = (String) jcbStationsPlotted.getItemAt(i);
294                        if ((s != null) && s.equals(station)) {
295                        JOptionPane.showMessageDialog(null, 
296                                "A station with this label has already been plotted: " + s);
297                    return;
298                        }
299                }
300                // if we made it this far, fields are valid, we can create a custom ground station
301                // create new earth location, add it to stations plotted, set index, 
302                        jcbStationsPlotted.addItem(labStr);
303                        jcbStationsPlotted.setSelectedItem(labStr);
304                        // make an Earth location
305                        double dAlt = dataSource.getNearestAltToGroundStation(latitude, longitude) / 1000.0;
306                        EarthLocationTuple elt = null;
307                        try {
308                                elt = new EarthLocationTuple(fLat, fLon, dAlt);
309                        } catch (RemoteException e) {
310                                e.printStackTrace();
311                        } catch (VisADException e) {
312                                e.printStackTrace();
313                        }
314                        stationMap.put(labStr, elt);
315                        plotCoverageCircles();
316                        updateDisplayList();
317                return;
318        }
319        
320        // user trying to add a new ground station to those plotted on display
321        if (STATION_ADD.equals(ae.getActionCommand())) {
322                logger.debug("Add Station...");
323                String station = (String) locationComboBox.getSelectedItem();
324                boolean alreadyPlotted = false;
325                int numPlotted = jcbStationsPlotted.getItemCount();
326                for (int i = 0; i < numPlotted; i++) {
327                        String s = (String) jcbStationsPlotted.getItemAt(i);
328                        if ((s != null) && s.equals(station)) {
329                                alreadyPlotted = true;
330                                break;
331                        }
332                }
333                if (alreadyPlotted) {
334                JOptionPane.showMessageDialog(null, 
335                        "Station already plotted on display: " + station);
336                } else {
337                        jcbStationsPlotted.addItem(station);
338                        jcbStationsPlotted.setSelectedItem(station);
339                        plotCoverageCircles();
340                }
341                updateDisplayList();
342                return;
343        }
344        
345        // user removing a ground station from the display
346        if (STATION_REM.equals(ae.getActionCommand())) {
347                logger.debug("Rem Station...");
348                String station = (String) jcbStationsPlotted.getSelectedItem();
349                if (station == null) {
350                JOptionPane.showMessageDialog(null, 
351                        "Nothing to remove");
352                } else {
353                        jcbStationsPlotted.removeItem(station);
354                        plotCoverageCircles();
355                }
356                updateDisplayList();
357                return;
358        }
359        
360        // swath-related changes
361        if (SWATH_MODS.equals(ae.getActionCommand())) {
362                logger.debug("Apply Swath Mods...");
363                
364                boolean fontChanged = true;
365                boolean swathChanged = false;
366                curSwathCenterWidth = jcbSCLineWidth.getSelectedIndex() + 1;
367                curSwathEdgeWidth = jcbSELineWidth.getSelectedIndex() + 1;
368                if (curSwathCenterWidth != prvSwathCenterWidth) swathChanged = true;
369                if (curSwathEdgeWidth != prvSwathEdgeWidth) swathChanged = true;
370                
371                curTrackLineStyle = jcbTrackLineStyle.getSelectedIndex();
372                if (curTrackLineStyle != prvTrackLineStyle) swathChanged = true; 
373                curEdgeLineStyle = jcbEdgeLineStyle.getSelectedIndex();
374                if (curEdgeLineStyle != prvEdgeLineStyle) swathChanged = true;
375                
376                curSwathColor = colorSwatch.getColor();
377                if (! curSwathColor.equals(prvSwathColor)) swathChanged = true;
378                
379                int newSwathWidth = validateSwathWidthField();
380                if (newSwathWidth > 0) {
381                        curWidth = newSwathWidth;
382                        if (curWidth != prvWidth) swathChanged = true;
383                }
384                
385                // update font attributes if necessary
386                Font f = otFontSelector.getFont();
387                if (! f.equals(otCurFont)) {
388                        otCurFont = f;
389                        fontChanged = true;
390                }
391                
392                // see if label interval has changed
393                SpinnerNumberModel snm = (SpinnerNumberModel) (js.getModel());
394                int tmpLabelInterval = ((Integer) snm.getValue()).intValue();
395                if ((tmpLabelInterval != labelInterval) || fontChanged) {
396                        logger.debug("Label interval change from: " + labelInterval +
397                                        " to: " + tmpLabelInterval);
398                        labelInterval = tmpLabelInterval;
399                        try {
400                                // remove the current set of labels
401                                int numLabels = timeLabelDsp.displayableCount();
402                                for (int i = 0; i < numLabels; i++) {
403                                        timeLabelDsp.removeDisplayable(0);
404                                }
405                                // get the currently loaded data
406                                Data data = getData(getDataInstance());
407                                        if (data instanceof Tuple) {
408                                Data[] dataArr = ((Tuple) data).getComponents();
409
410                                int npts = dataArr.length;
411                                double distance = 0.0d;
412                                LatLonTuple prvPoint = null;
413
414                                for (int i = 0; i < npts; i++) {
415                                    Tuple t = (Tuple) dataArr[i];
416                                    Data[] tupleComps = t.getComponents();
417
418                                    LatLonTuple llt = (LatLonTuple) tupleComps[1];
419                                    double dlat = llt.getLatitude().getValue();
420                                    double dlon = llt.getLongitude().getValue();
421
422                                if ((i % labelInterval) == 0) {
423                                        
424                                            if (prvPoint != null) {
425                                                distance = Util.distance(prvPoint, llt);
426                                                if (distance < LABEL_DISTANCE_THRESHOLD) {
427                                                        continue;
428                                                }
429                                            }
430                                            
431                                    String str = ((Text) tupleComps[0]).getValue();
432                                    logger.debug("Adding time for str: " + str);
433                                    int indx = str.indexOf(" ") + 1;
434                                    String subStr = "- " + str.substring(indx, indx+5);
435                                    TextDisplayable time = new TextDisplayable(SWATH_MODS, otTextType);
436                                    time.setJustification(TextControl.Justification.LEFT);
437                                    time.setVerticalJustification(TextControl.Justification.CENTER);
438                                    time.setColor(curSwathColor);
439                                    time.setFont(otFontSelector.getFont());
440                                    time.setTextSize((float) otFontSelector.getFontSize() / FONT_SCALE_FACTOR);
441                                    time.setSphere(inGlobeDisplay());
442                                    
443                                    RealTuple lonLat =
444                                        new RealTuple(RealTupleType.SpatialEarth2DTuple,
445                                            new double[] { dlon, dlat });
446                                    Tuple tup = new Tuple(makeTupleType(SWATH_MODS),
447                                        new Data[] { lonLat, new Text(otTextType, subStr)});
448                                    time.setData(tup);
449                                    timeLabelDsp.addDisplayable(time);
450                                    
451                                        prvPoint = llt;
452                                }
453
454                                }
455                            }
456                                        
457                                // check swath width field, update if necessary
458                                if (swathChanged) changeSwathWidth();
459                                
460                                } catch (RemoteException re) {
461                                        re.printStackTrace();
462                                } catch (VisADException vade) {
463                                        vade.printStackTrace();
464                                }
465                }
466                
467                updateDisplayList();
468                return;
469        }
470        
471        // Ground station mods
472        if (STATION_MODS.equals(ae.getActionCommand())) {
473                
474                logger.debug("Apply Station Mods...");
475                
476                // flag indicates user changed some parameter
477                boolean somethingChanged = true;
478                
479                setAntColor(antColorSwatch.getColor());
480                
481                // update font attributes if necessary
482                Font f = gsFontSelector.getFont();
483                if (! f.equals(gsCurFont)) {
484                        gsCurFont = f;
485                        somethingChanged = true;
486                }
487                
488                // validate antenna angle text field, redraw if necessary
489            String s = antennaAngle.getText();
490            int newAngle = curAngle;
491            try {
492                newAngle = Integer.parseInt(s);
493                if (newAngle != curAngle) {
494                        curAngle = newAngle;
495                        somethingChanged = true;
496                }
497            } catch (NumberFormatException nfe) {
498                JOptionPane.showMessageDialog(latLonAltPanel, 
499                        "Antenna angle valid range is " + DEFAULT_ANTENNA_ANGLE + 
500                        " to " + MAX_ANTENNA_ANGLE + " degrees");
501                return;
502            }
503            
504            if (somethingChanged) {             
505                plotCoverageCircles();  
506            }
507            
508            updateDisplayList();
509                return;
510                
511        }
512
513    }
514
515    /**
516     * Apply the map (height) position to the displays
517     */
518    
519    private void applyTrackPosition() {
520        try {
521            DisplayRealType dispType = navDsp.getDisplayAltitudeType();
522            trackDsp.setConstantPosition(trackZ, dispType);
523            timeLabelDsp.setConstantPosition(trackZ, dispType);
524            stationLabelDsp.setConstantPosition(gsZ, dispType);
525        } catch (Exception e) {
526            e.printStackTrace();
527        }
528    }
529
530    private void changeSwathWidth() {
531
532        logger.debug("changeSwathWidth() in...");
533        if ((curWidth != prvWidth) || 
534                        (curTrackLineStyle != prvTrackLineStyle) ||
535                        (curEdgeLineStyle != prvEdgeLineStyle) ||
536                        (curSwathCenterWidth != prvSwathCenterWidth) ||
537                        (curSwathEdgeWidth != prvSwathEdgeWidth) ||
538                        (curSwathColor != prvSwathColor)) {
539                prvWidth = curWidth;
540                prvSwathCenterWidth = curSwathCenterWidth;
541                prvSwathEdgeWidth = curSwathEdgeWidth;
542                prvSwathColor = curSwathColor;
543                prvTrackLineStyle = curTrackLineStyle;
544                prvEdgeLineStyle = curEdgeLineStyle;
545                try {
546                        removeDisplayable(swathEdgeDsp);
547                        removeDisplayable(trackDsp);
548                        swathEdgeDsp = null;
549                        trackDsp = null;
550                        Data data = getData(getDataInstance());
551                        swathEdgeDsp = new CompositeDisplayable();
552                        trackDsp = new CompositeDisplayable();
553                        createTrackDisplay(data, true);
554                } catch (Exception e) {
555                        e.printStackTrace();
556                }
557        }
558    }
559    
560    private void createTrackDisplay(Data data, boolean doTrack) {
561        logger.debug("createTrackDisplay() in...");
562        try {
563            List<String> dts = new ArrayList<String>();
564            if (data instanceof Tuple) {
565                Data[] dataArr = ((Tuple) data).getComponents();
566
567                int npts = dataArr.length;
568                float[][] latlon = new float[2][npts];
569                double distance = 0.0d;
570                LatLonTuple prvPoint = null;
571
572                for (int i = 0; i < npts; i++) {
573                    Tuple t = (Tuple) dataArr[i];
574                    Data[] tupleComps = t.getComponents();
575
576                    LatLonTuple llt = (LatLonTuple) tupleComps[1];
577                    double dlat = llt.getLatitude().getValue();
578                    double dlon = llt.getLongitude().getValue();
579
580                    if (doTrack) {
581                        if ((i % labelInterval) == 0) {
582                                    
583                                if (prvPoint != null) {
584                                        distance = Util.distance(prvPoint, llt);
585                                        logger.debug("Distance: " + distance);
586                                        if (distance < LABEL_DISTANCE_THRESHOLD) {
587                                    latlon[0][i] = (float) dlat;
588                                    latlon[1][i] = (float) dlon;
589                                                continue;
590                                        }
591                                }
592                                
593                            String str = ((Text) tupleComps[0]).getValue();
594                            dts.add(str);
595                            int indx = str.indexOf(" ") + 1;
596                            String subStr = "- " + str.substring(indx, indx + 5);
597                            TextDisplayable time = new TextDisplayable(SWATH_MODS, otTextType);
598                            time.setJustification(TextControl.Justification.LEFT);
599                            time.setVerticalJustification(TextControl.Justification.CENTER);
600                            time.setColor(curSwathColor);
601                                time.setTextSize((float) otFontSelector.getFontSize() / FONT_SCALE_FACTOR);
602                                time.setFont(otFontSelector.getFont());
603                                time.setSphere(inGlobeDisplay());
604                            
605                            RealTuple lonLat =
606                                new RealTuple(RealTupleType.SpatialEarth2DTuple,
607                                    new double[] { dlon, dlat });
608                            Tuple tup = new Tuple(makeTupleType(SWATH_MODS),
609                                new Data[] { lonLat, new Text(otTextType, subStr)});
610                            time.setData(tup);
611                            timeLabelDsp.addDisplayable(time);
612                            
613                            prvPoint = llt;
614                        }
615                    }
616                    latlon[0][i] = (float) dlat;
617                    latlon[1][i] = (float) dlon;
618                }
619
620                if (doTrack) {
621                    Gridded2DSet track = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
622                               latlon, npts);
623                    SampledSet[] set = new SampledSet[1];
624                    set[0] = track;
625                    UnionSet uset = new UnionSet(set);
626                    CurveDrawer trackLines = new CurveDrawer(uset);
627                    trackLines.setData(uset);
628                    trackLines.setDrawingEnabled(false);
629                    trackLines.setLineStyle(jcbTrackLineStyle.getSelectedIndex());
630                    trackDsp.addDisplayable(trackLines);
631                    trackDsp.setColor(curSwathColor);
632                    trackDsp.setLineWidth(jcbSCLineWidth.getSelectedIndex() + 1);
633
634                    addDisplayable(trackDsp, FLAG_COLORTABLE);
635                    addDisplayable(timeLabelDsp, FLAG_COLORTABLE);
636                    addDisplayable(stationLabelDsp, FLAG_COLORTABLE);
637                }
638
639                float[][][] crv = getSwath(latlon);
640                int npt = crv[0][0].length;
641                float[][] leftC = new float[2][npt];
642                float[][] rightC = new float[2][npt];
643                for (int i = 0; i < npt; i++) {
644                    leftC[0][i] = crv[0][0][i];
645                    leftC[1][i] = crv[0][1][i];
646                    rightC[0][i] = crv[1][0][i];
647                    rightC[1][i] = crv[1][1][i];
648                }
649                Gridded2DSet left = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
650                           leftC, npt);
651                SampledSet[] lSet = new SampledSet[1];
652                lSet[0] = left;
653                UnionSet lUSet = new UnionSet(lSet);
654                CurveDrawer leftLines = new CurveDrawer(lUSet);
655                leftLines.setLineStyle(jcbEdgeLineStyle.getSelectedIndex());
656                leftLines.setData(lUSet);
657                swathEdgeDsp.addDisplayable(leftLines);
658                leftLines.setDrawingEnabled(false);
659
660                Gridded2DSet right = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
661                           rightC, npt);
662                SampledSet[] rSet = new SampledSet[1];
663                rSet[0] = right;
664                UnionSet rUSet = new UnionSet(rSet);
665                CurveDrawer rightLines = new CurveDrawer(rUSet);
666                rightLines.setLineStyle(jcbEdgeLineStyle.getSelectedIndex());
667                rightLines.setData(rUSet);
668                swathEdgeDsp.addDisplayable(rightLines);
669                rightLines.setDrawingEnabled(false);
670                
671                swathEdgeDsp.setColor(curSwathColor);
672                swathEdgeDsp.setLineWidth(curSwathEdgeWidth);
673                addDisplayable(swathEdgeDsp, FLAG_COLORTABLE);
674            }
675        } catch (Exception e) {
676            e.printStackTrace();
677        }
678        return;
679    }
680
681    /**
682     * Called by doMakeWindow in DisplayControlImpl, which then calls its
683     * doMakeMainButtonPanel(), which makes more buttons.
684     *
685     * @return container of contents
686     */
687    
688    public Container doMakeContents() {
689        
690        fontSizePanel = new JPanel();
691        fontSizePanel.setLayout(new BoxLayout(fontSizePanel, BoxLayout.Y_AXIS));
692        JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
693        jcbLabels = new JCheckBox("Labels On/Off");
694        jcbLabels.setSelected(true);
695        jcbLabels.setName(CHECKBOX_LABELS);
696        jcbLabels.addItemListener(this);
697        labelPanel.add(jcbLabels);
698        
699        // same row, add label interval spinner
700        Integer defaultInterval = new Integer(5); 
701        Integer minInterval = new Integer(1);
702        Integer maxInterval = new Integer(120); 
703        Integer intervalStep = new Integer(1); 
704        SpinnerNumberModel snm = 
705                        new SpinnerNumberModel(defaultInterval, minInterval, maxInterval, intervalStep);
706        JLabel intervalLabel = new JLabel("Label Interval:");
707        JLabel intervalUnits = new JLabel("minutes");
708        js = new JSpinner(snm);
709        labelPanel.add(Box.createHorizontalStrut(5));
710        labelPanel.add(intervalLabel);
711        labelPanel.add(js);
712        labelPanel.add(intervalUnits);
713        
714        // line style for drawing swath track and width edges
715        jcbTrackLineStyle.addActionListener(this);
716        // init to solid
717        jcbTrackLineStyle.setSelectedIndex(0);
718        curTrackLineStyle = jcbTrackLineStyle.getSelectedIndex();
719
720        jcbEdgeLineStyle.addActionListener(this);
721        // init to dashed
722        jcbEdgeLineStyle.setSelectedIndex(1);
723        curEdgeLineStyle = jcbEdgeLineStyle.getSelectedIndex();
724        
725        fontSizePanel.add(labelPanel);
726        JPanel botPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
727        botPanel.add(new JLabel("Font: "));
728        botPanel.add(otFontSelector.getComponent());
729        fontSizePanel.add(botPanel);
730
731        colorSwatch = new ColorSwatchComponent(getStore(), curSwathColor, "Color");
732        colorSwatch.setPreferredSize(new Dimension(30, 30));
733        
734        colorPanel = new JPanel(new FlowLayout(FlowLayout.LEADING));
735        colorPanel.add(new JLabel("Color: "));
736        colorPanel.add(colorSwatch);
737        
738        colorPanel.add(Box.createHorizontalStrut(5));
739        colorPanel.add(new JLabel("Track Width: "));
740        colorPanel.add(jcbSCLineWidth);
741        
742        colorPanel.add(Box.createHorizontalStrut(4));
743        colorPanel.add(new JLabel("Track Style: "));
744        colorPanel.add(jcbTrackLineStyle);
745        
746        colorPanel.add(Box.createHorizontalStrut(5));
747        colorPanel.add(new JLabel("Edge Width: "));
748        colorPanel.add(jcbSELineWidth);
749        
750        colorPanel.add(Box.createHorizontalStrut(4));
751        colorPanel.add(new JLabel("Edge Style: "));
752        colorPanel.add(jcbEdgeLineStyle);
753        
754        JPanel groundStationPanel = makeGroundStationPanel();
755
756        swathWidthPanel = makeSwathWidthPanel();
757        
758        JPanel outerPanel = new JPanel(new MigLayout());
759        
760        JPanel mainPanel = new JPanel(new MigLayout());
761        mainPanel.setBorder(BorderFactory.createTitledBorder(" Swath Controls "));
762        mainPanel.add(swathWidthPanel, "wrap");
763        mainPanel.add(fontSizePanel, "wrap");
764        mainPanel.add(colorPanel, "wrap");
765        JPanel bottomPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
766        JButton applySwathMods = new JButton("Apply");
767        applySwathMods.setActionCommand(SWATH_MODS);
768        applySwathMods.addActionListener(this);
769        bottomPanel.add(applySwathMods);
770        mainPanel.add(bottomPanel);
771        
772        outerPanel.add(mainPanel, "wrap");
773        outerPanel.add(groundStationPanel, "wrap");
774        
775        return outerPanel;
776    }
777
778    private CurveDrawer makeCoverageCircle(double lat, double lon, double satAlt, Color color) {
779
780        /* mean Earth radius in km */
781        double earthRadius = AstroConst.R_Earth_mean / 1000.0;
782        satAlt += earthRadius;
783        double SAC = Math.PI / 2.0 + Math.toRadians(curAngle);
784        double sinASC = earthRadius * Math.sin(SAC) / satAlt;
785        double dist = earthRadius * (Math.PI - SAC - Math.asin(sinASC));
786        double rat = dist / earthRadius;
787
788        int npts = 360;
789        float[][] latlon = new float[2][npts];
790        double cosDist = Math.cos(rat);
791        double sinDist = Math.sin(rat);
792        double sinLat = Math.sin(lat);
793        double cosLat = Math.cos(lat);
794        double sinLon = -Math.sin(lon);
795        double cosLon = Math.cos(lon);
796        for (int i = 0; i < npts; i++) {
797            double azimuth = Math.toRadians((double) i);
798            double cosBear = Math.cos(azimuth);
799            double sinBear = Math.sin(azimuth);
800            double z = cosDist * sinLat +
801                       sinDist * cosLat * cosBear;
802            double y = cosLat * cosLon * cosDist +
803                       sinDist * (sinLon * sinBear - sinLat * cosLon * cosBear);
804            double x = cosLat * sinLon * cosDist -
805                       sinDist * (cosLon * sinBear + sinLat * sinLon * cosBear);
806            double r = Math.sqrt(x * x + y * y);
807            double latRad = Math.atan2(z, r);
808            double lonRad = 0.0;
809            if (r > 0.0) lonRad = -Math.atan2(x, y);
810            latlon[0][i] = (float) Math.toDegrees(latRad);
811            latlon[1][i] = (float) Math.toDegrees(lonRad);
812        }
813        CurveDrawer coverageCircle = null;
814        try {
815            Gridded2DSet circle = new Gridded2DSet(RealTupleType.LatitudeLongitudeTuple,
816                               latlon, npts);
817            SampledSet[] set = new SampledSet[1];
818            set[0] = circle;
819            UnionSet uset = new UnionSet(set);
820            coverageCircle = new CurveDrawer(uset);
821            coverageCircle.setLineWidth(jcbGSLineWidth.getSelectedIndex() + 1);
822            coverageCircle.setLineStyle(jcbStationLineStyle.getSelectedIndex());
823            coverageCircle.setColor(getAntColor());
824            coverageCircle.setData(uset);
825            coverageCircle.setDrawingEnabled(false);
826            coverageCircle.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType());
827        } catch (Exception e) {
828            e.printStackTrace();
829            return null;
830        }
831        return coverageCircle;
832    }
833
834    public Color getAntColor() {
835        if (antColor == null) antColor = defaultAntColor;
836        return antColor;
837    }
838
839    public PolarOrbitTrackDataSource getDataSource() {
840        DataSourceImpl ds = null;
841        List dataSources = getDataSources();
842        boolean gotit = false;
843        if (!dataSources.isEmpty()) {
844            int nsrc = dataSources.size();
845            for (int i = 0; i < nsrc; i++) {
846                ds = (DataSourceImpl) dataSources.get(nsrc - i - 1);
847                if (ds instanceof PolarOrbitTrackDataSource) {
848                    gotit = true;
849                    break;
850                }
851            }
852        }
853        if (!gotit) return null;
854        return (PolarOrbitTrackDataSource) ds;
855    }
856
857        /* (non-Javadoc)
858         * @see ucar.unidata.idv.control.DisplayControlImpl#getDisplayListData()
859         */
860        @Override
861        protected Data getDisplayListData() {
862                // get time range that was specified in the Field Selector
863                String startTime = (String) getDataInstance().getDataSelection().getProperties().get(TimeRangeSelection.PROP_BEGTIME);
864                String endTime = (String) getDataInstance().getDataSelection().getProperties().get(TimeRangeSelection.PROP_ENDTIME);
865                
866                // get the template used for the Display Properties Layer Label
867                String labelTemplate = getDisplayListTemplate();
868
869                // see if time macro is enabled
870                boolean hasTimeMacro = UtcDate.containsTimeMacro(labelTemplate);
871                
872                // fetch the label superclass would normally generate
873                Data data = super.getDisplayListData();
874                
875                // if so, modify label with time range for this selection
876                if (hasTimeMacro) {
877                        try {
878                                TextType tt = TextType.getTextType(DISPLAY_LIST_NAME);
879                                data  = new Text(tt, data.toString() + startTime + " - " + endTime);
880                        } catch (VisADException vade) {
881                                vade.printStackTrace();
882                        }
883                }
884                
885                // return either original or modified data object
886                return data;
887        }
888
889        public double getLatitude() {
890        return latitude;
891    }
892
893    public double getLongitude() {
894        return longitude;
895    }
896
897    public String getStation() {
898        return station;
899    }
900
901    private float[][][] getSwath(float[][] track) {
902        double earthRadius = AstroConst.R_Earth_mean / 1000.0;
903        int npt = track[0].length-1;
904        float[][][] ret = new float[2][2][npt - 1];
905        try {
906            int indx = 0;
907            for (int i = 1; i < npt; i++) {
908                double latA = Math.toRadians(track[0][i - 1]);
909                double lonA = Math.toRadians(track[1][i - 1]);
910
911                double latB = Math.toRadians(track[0][i + 1]);
912                double lonB = Math.toRadians(track[1][i + 1]);
913
914                double diffLon = lonB - lonA;
915                double bX = Math.cos(latB) * Math.cos(diffLon);
916                double bY = Math.cos(latB) * Math.sin(diffLon);
917                double xFac = Math.cos(latA) + bX;
918                double latC = Math.atan2(Math.sin(latA) + Math.sin(latB), Math.sqrt(xFac * xFac + bY * bY));
919                double lonC = lonA + Math.atan2(bY, xFac);
920
921                double bearing = Math.atan2(Math.sin(diffLon) * Math.cos(latB),
922                                 Math.cos(latA) * Math.sin(latB) - Math.sin(latA) * Math.cos(latB) * Math.cos(diffLon))
923                                 + Math.PI / 2.0;
924                double dist = curWidth / 2.0;
925                dist /= earthRadius;
926                double lat = Math.asin(Math.sin(latC) * Math.cos(dist) +
927                                       Math.cos(latC) * Math.sin(dist) * Math.cos(bearing));
928                double lon = lonC + Math.atan2(Math.sin(bearing) * Math.sin(dist) * Math.cos(latC),
929                                               Math.cos(dist) - Math.sin(latC) * Math.sin(lat));
930                float latD = (float) Math.toDegrees(lat);
931                float lonD = (float) Math.toDegrees(lon);
932
933                bearing += Math.PI;
934                lat = Math.asin(Math.sin(latC) * Math.cos(dist) +
935                                       Math.cos(latC) * Math.sin(dist) * Math.cos(bearing));
936                lon = lonC + Math.atan2(Math.sin(bearing) * Math.sin(dist) * Math.cos(latC),
937                                               Math.cos(dist) - Math.sin(latC) * Math.sin(lat));
938                float latE = (float) Math.toDegrees(lat);
939                float lonE = (float) Math.toDegrees(lon);
940
941                ret[0][0][indx] = latD;
942                ret[0][1][indx] = lonD;
943
944                ret[1][0][indx] = latE;
945                ret[1][1][indx] = lonE;
946                ++indx;
947            }
948        } catch (Exception e) {
949            e.printStackTrace();
950            return null;
951        }
952        return ret;
953    }
954
955    @Override public boolean init(DataChoice dataChoice) 
956        throws VisADException, RemoteException 
957    {
958        logger.debug("init() in...");
959        
960        PolarOrbitTrackDataSource potdc = getDataSource();
961
962        // validate time range before going ahead with control initialization
963        if (! potdc.getTrs().begTimeOk()) {
964                JOptionPane.showMessageDialog(null, 
965                                "Invalid start time, must follow format HH:MM:SS", 
966                                "Time Range Selection Error", JOptionPane.ERROR_MESSAGE);
967                return false;
968        }
969        
970        if (! potdc.getTrs().endTimeOk()) {
971                JOptionPane.showMessageDialog(null, 
972                                "Invalid end time, must follow format HH:MM:SS", 
973                                "Time Range Selection Error", JOptionPane.ERROR_MESSAGE);
974                return false;
975        }
976        
977        if (! potdc.getTrs().timeRangeOk()) {
978                JOptionPane.showMessageDialog(null, 
979                                "Invalid time range selection, please correct", 
980                                "Time Range Selection Error", JOptionPane.ERROR_MESSAGE);
981                return false;
982        }
983        
984        // instantiate components we need to exist at initialization
985        latLabel = new JLabel();
986        lonLabel = new JLabel();
987        altLabel = new JLabel();
988        String [] lineWidths = {"1", "2", "3", "4"};
989        jcbGSLineWidth = new JComboBox(lineWidths);
990        jcbSCLineWidth = new JComboBox(lineWidths);
991        // initialize swath center (track line) to width 2
992        jcbSCLineWidth.setSelectedIndex(1);
993        jcbEdgeLineStyle.setSelectedIndex(1);
994        jcbSELineWidth = new JComboBox(lineWidths);
995        otFontSelector = new FontSelector(FontSelector.COMBOBOX_UI, false, false);
996        otFontSelector.setFont(FontSelector.DEFAULT_FONT);
997        gsFontSelector = new FontSelector(FontSelector.COMBOBOX_UI, false, false);
998        gsFontSelector.setFont(FontSelector.DEFAULT_FONT);
999        this.dataChoice = dataChoice;
1000        String choiceName = dataChoice.getName();
1001        NodeList nodeList = root.getElementsByTagName(TAG_SATELLITE);
1002        int num = nodeList.getLength();
1003        if (num > 0) {
1004            for (int i = 0; i < num; i++) {
1005                Element n = (Element) (nodeList.item(i));
1006                String satName = n.getAttribute(ATTR_NAME);
1007                if (satName.equals(choiceName)) {
1008                    String strWidth = n.getAttribute(ATTR_WIDTH);
1009                    if (strWidth.isEmpty()) strWidth = "0";
1010                    Double dWidth = new Double(strWidth);
1011                    curWidth = dWidth.doubleValue();
1012                    break;
1013                }
1014            }
1015        }
1016        try {
1017            trackDsp = new CompositeDisplayable();
1018            timeLabelDsp = new CompositeDisplayable();
1019            stationLabelDsp = new CompositeDisplayable();
1020            swathEdgeDsp = new CompositeDisplayable();
1021            circleDsp = new CompositeDisplayable();
1022        } catch (Exception e) {
1023            logger.error("problem creating composite displayable");
1024            e.printStackTrace();
1025            return false;
1026        }
1027        boolean result = super.init((DataChoice) this.getDataChoices().get(0));
1028
1029        String dispName = getDisplayName();
1030        setDisplayName(getLongParamName() + " " + dispName);
1031        logger.debug("Setting display name: " + getDisplayName());
1032        try {
1033            String longName = getLongParamName().replaceAll(" ", "");
1034            otTextType = new TextType(SWATH_MODS + longName);
1035            gsTextType = new TextType(STATION_MODS + longName);
1036        } catch (Exception e) {
1037                e.printStackTrace();
1038            otTextType = TextType.Generic;
1039            gsTextType = TextType.Generic;
1040        }
1041
1042        Data data = getData(getDataInstance());
1043        createTrackDisplay(data, true);
1044        dataSource = getDataSource();
1045        try {
1046            navDsp = getNavigatedDisplay();
1047            float defaultZ = getMapViewManager().getDefaultMapPosition();
1048            // we're just nudging a bit so tracks (and their labels) get drawn over
1049            // ground stations (and their labels), which get drawn over default map level
1050            // user can change this in map controls if they prefer maps on top
1051            gsZ = defaultZ + 0.25f;
1052            trackZ = defaultZ + 0.5f;
1053            // range on "map level" stuff is -1 to 1, stay within these limits
1054            if (trackZ > 1.0f) trackZ = 1.0f;
1055            if (gsZ > 1.0f) gsZ = 1.0f;
1056            applyTrackPosition();
1057        } catch (Exception e) {
1058            logger.error("get display center e=" + e);
1059        }
1060
1061        return result;
1062    }
1063
1064    public void itemStateChanged(ItemEvent ie) {
1065
1066        // now we got multiple checkboxes, so first see which one applies
1067        String source = ((JCheckBox) ie.getSource()).getName();
1068        try {
1069                if (source.equals(CHECKBOX_LABELS)) {
1070                        if (ie.getStateChange() == ItemEvent.DESELECTED) {
1071                                timeLabelDsp.setVisible(false);
1072                        } else {
1073                                timeLabelDsp.setVisible(true);
1074                        }
1075                                updateDisplayList();
1076                }
1077                if (source.equals(CHECKBOX_SWATH_EDGES)) {
1078                        if (ie.getStateChange() == ItemEvent.DESELECTED) {
1079                                swathEdgeDsp.setVisible(false);
1080                        } else {
1081                                swathEdgeDsp.setVisible(true);
1082                        }
1083                                updateDisplayList();
1084                }
1085        } catch (RemoteException re) {
1086                re.printStackTrace();
1087        } catch (VisADException vade) {
1088                vade.printStackTrace();
1089        }
1090
1091    }
1092
1093    private void labelGroundStation(String station) {
1094        try {
1095                String str = "+ " + station;
1096                logger.debug("Drawing station: " + str);
1097
1098                TextDisplayable groundStationDsp = new TextDisplayable(STATION_MODS, gsTextType);
1099                groundStationDsp.setJustification(TextControl.Justification.LEFT);
1100                groundStationDsp.setVerticalJustification(TextControl.Justification.CENTER);
1101                groundStationDsp.setColor(getAntColor());
1102                groundStationDsp.setFont(gsFontSelector.getFont());
1103                groundStationDsp.setTextSize((float) gsFontSelector.getFontSize() / FONT_SCALE_FACTOR);
1104                groundStationDsp.setSphere(inGlobeDisplay());
1105
1106                double dlat = getLatitude();
1107                double dlon = getLongitude();
1108                RealTuple lonLat =
1109                                new RealTuple(RealTupleType.SpatialEarth2DTuple,
1110                                                new double[] { dlon, dlat });
1111                Tuple tup = new Tuple(makeTupleType(STATION_MODS),
1112                                new Data[] { lonLat, new Text(gsTextType, str)});
1113                groundStationDsp.setData(tup);
1114                groundStationDsp.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType());
1115                stationLabelDsp.addDisplayable(groundStationDsp);
1116        } catch (Exception e) {
1117                e.printStackTrace();
1118        }
1119    }
1120
1121    private JPanel makeGroundStationPanel() {
1122        
1123        JPanel jp = new JPanel(new MigLayout());
1124        jp.setBorder(BorderFactory.createTitledBorder(" Ground Station Controls "));
1125
1126        // line style for drawing coverage circles
1127//        jcbStationLineStyle = new JComboBox(new String[] { "_____", "_ _ _", ".....", "_._._" });
1128        jcbStationLineStyle = new JComboBox(lineStyles);
1129        jcbStationLineStyle.addActionListener(this);
1130        jcbStationLineStyle.setSelectedIndex(1);
1131        prvStationLineStyle = jcbStationLineStyle.getSelectedIndex();
1132        
1133        locationComboBox = new JComboBox();
1134        jcbStationsPlotted = new JComboBox();
1135
1136        // Ground Stations are now a natural-order map (alphabetical)
1137        GroundStations gs = new GroundStations(null);
1138        stationMap = gs.getGroundStations();
1139        TreeSet<String> keySet = new TreeSet<String>(stationMap.keySet());
1140
1141        GuiUtils.setListData(locationComboBox, keySet.toArray());
1142
1143        // initialize with first Earth Location in our map
1144        if (! stationMap.isEmpty()) {
1145                EarthLocationTuple elt = stationMap.get(locationComboBox.getSelectedItem());
1146                latLabel.setText(elt.getLatitude().toString());
1147                lonLabel.setText(elt.getLongitude().toString());
1148                altLabel.setText(elt.getAltitude().toString());
1149        }
1150        
1151        locationPanel = new JPanel();
1152        locationPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
1153        locationPanel.add(new JLabel("Ground Stations Available:"));
1154        locationPanel.add(locationComboBox);
1155        JButton addButton = new JButton("Add Selected");
1156        addButton.setActionCommand(STATION_ADD);
1157        addButton.addActionListener(this);
1158        locationPanel.add(addButton);
1159        
1160        JPanel customPanel = new JPanel();
1161        customPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
1162        customPanel.add(new JLabel("Custom Ground Station:    Label: "));
1163        customLab = new JTextField(6);
1164        customPanel.add(customLab);
1165        customPanel.add(new JLabel("Latitude: "));
1166        customLat = new JTextField(6);
1167        customPanel.add(customLat);
1168        customPanel.add(new JLabel("Longitude: "));
1169        customLon = new JTextField(6);
1170        customPanel.add(customLon);
1171        JButton customButton = new JButton("Add Custom");
1172        customButton.setActionCommand(CUSTOM_ADD);
1173        customButton.addActionListener(this);
1174        customPanel.add(customButton);
1175        
1176        JPanel plottedStationsPanel = new JPanel();
1177        plottedStationsPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
1178        plottedStationsPanel.add(new JLabel("Ground Stations Plotted:"));
1179        plottedStationsPanel.add(jcbStationsPlotted);
1180        JButton remButton = new JButton("Remove Selected");
1181        remButton.setActionCommand(STATION_REM);
1182        remButton.addActionListener(this);
1183        plottedStationsPanel.add(remButton);
1184        
1185        latLonAltPanel = new JPanel();
1186        latLonAltPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
1187        
1188        latLonAltPanel.add(new JLabel("Last Station Plotted,  Latitude: "));
1189        latLonAltPanel.add(latLabel);
1190        latLonAltPanel.add(Box.createHorizontalStrut(5));
1191        
1192        latLonAltPanel.add(new JLabel("Longitude: "));
1193        latLonAltPanel.add(lonLabel);
1194        latLonAltPanel.add(Box.createHorizontalStrut(5));
1195        
1196        latLonAltPanel.add(new JLabel("Altitude: "));
1197        latLonAltPanel.add(altLabel);
1198        
1199        JPanel gsFontPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
1200        gsFontPanel.add(new JLabel("Font: "));
1201        gsFontPanel.add(gsFontSelector.getComponent());
1202        
1203        Color swatchAntColor = getAntColor();
1204        antColorSwatch = new ColorSwatchComponent(getStore(), swatchAntColor, "Color");
1205        antColorSwatch.setPreferredSize(new Dimension(30, 30));
1206        
1207        antColorPanel = new JPanel();
1208        antColorPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
1209        antColorPanel.add(new JLabel("Color: "));
1210        antColorPanel.add(antColorSwatch);
1211        
1212        antColorPanel.add(Box.createHorizontalStrut(5));
1213        antColorPanel.add(new JLabel("Line Width: "));
1214        antColorPanel.add(jcbGSLineWidth);
1215        
1216        antColorPanel.add(Box.createHorizontalStrut(5));
1217        antColorPanel.add(new JLabel("Line Style: "));
1218        antColorPanel.add(jcbStationLineStyle);
1219        
1220        antColorPanel.add(Box.createHorizontalStrut(5));
1221        antColorPanel.add(new JLabel("Antenna Angle: "));
1222        antColorPanel.add(antennaAngle);
1223        
1224        jp.add(locationPanel, "wrap");
1225        jp.add(customPanel, "wrap");
1226        jp.add(plottedStationsPanel, "wrap");
1227        jp.add(latLonAltPanel, "wrap");
1228        jp.add(Box.createVerticalStrut(5), "wrap");
1229        jp.add(gsFontPanel, "wrap");
1230        jp.add(antColorPanel, "wrap");
1231        
1232        JPanel bottomRow = new JPanel(new FlowLayout(FlowLayout.LEFT));
1233        JButton applyGroundStationMods = new JButton("Apply");
1234        applyGroundStationMods.setActionCommand(STATION_MODS);
1235        applyGroundStationMods.addActionListener(this);
1236        bottomRow.add(applyGroundStationMods);
1237        jp.add(bottomRow);
1238        
1239        return jp;
1240    }
1241
1242    private JPanel makeSwathWidthPanel() {
1243        if (dataChoice != null)
1244            satelliteName = new JLabel(dataChoice.getName());
1245        Integer isw = new Integer((int) curWidth);
1246        swathWidthFld = new JTextField(isw.toString(), 5);
1247
1248        JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT));
1249        
1250        // first on this panel, check box to turn on/off swath line edges
1251        jcbSwathEdges = new JCheckBox("Swath Edges On/Off");
1252        jcbSwathEdges.setSelected(true);
1253        jcbSwathEdges.setName(CHECKBOX_SWATH_EDGES);
1254        jcbSwathEdges.addItemListener(this);
1255        jp.add(jcbSwathEdges);
1256        
1257        jp.add(Box.createHorizontalStrut(5));
1258        jp.add(new JLabel("Satellite: "));
1259        jp.add(satelliteName);
1260        jp.add(Box.createHorizontalStrut(5));
1261        jp.add(new JLabel("Swath Width: "));
1262        jp.add(swathWidthFld);
1263        jp.add(kmLabel);
1264        jp.add(Box.createHorizontalStrut(5));
1265        
1266        return jp;
1267    }
1268
1269    private TupleType makeTupleType(String prefix) {
1270        TupleType t = null;
1271        try {
1272                if (prefix.equals(SWATH_MODS))
1273                        t = new TupleType(new MathType[] {RealTupleType.SpatialEarth2DTuple,
1274                                              otTextType});
1275                if (prefix.equals(STATION_MODS))
1276                        t = new TupleType(new MathType[] {RealTupleType.SpatialEarth2DTuple,
1277                                              gsTextType});
1278        } catch (Exception e) {
1279            e.printStackTrace();
1280        }
1281        return t;
1282    }
1283
1284    private void plotCoverageCircles() {
1285        try {
1286
1287                int num = circleDsp.displayableCount();
1288            for (int i = 0; i < num; i++) {
1289                // yes, always 0th it's a queue
1290                circleDsp.removeDisplayable(0);
1291            }
1292            removeDisplayable(circleDsp);
1293            circleDsp = null;
1294                int numLabels = stationLabelDsp.displayableCount();
1295                for (int i = 0; i < numLabels; i++) {
1296                        // yes, always 0th it's a queue
1297                        stationLabelDsp.removeDisplayable(0);
1298                }
1299
1300                int numPlotted = jcbStationsPlotted.getItemCount();
1301            circleDsp = new CompositeDisplayable();
1302            circleDsp.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType());
1303                for (int i = 0; i < numPlotted; i++) {
1304                        String s = (String) jcbStationsPlotted.getItemAt(i);
1305                EarthLocationTuple elt = stationMap.get(s);
1306                latLabel.setText(elt.getLatitude().toString());
1307                lonLabel.setText(elt.getLongitude().toString());
1308                altLabel.setText(elt.getAltitude().toString());
1309                // quick and easy way to limit sig digits to something not too crazy
1310                if (altLabel.getText().length() > 10) altLabel.setText(altLabel.getText().substring(0, 9));
1311                latitude = Double.parseDouble(latLabel.getText());
1312                longitude = Double.parseDouble(lonLabel.getText());
1313                setSatelliteAltitude(dataSource.getNearestAltToGroundStation(latitude, longitude) / 1000.0);
1314                
1315                CurveDrawer cd = makeCoverageCircle(Math.toRadians(latitude), Math.toRadians(longitude),
1316                        satelliteAltitude, getAntColor());
1317                if (cd != null) {
1318                        logger.debug("Drawing ground station, station name: " + s);
1319                    labelGroundStation(s);
1320                    circleDsp.setColor(getAntColor());
1321                    cd.setLineWidth(jcbGSLineWidth.getSelectedIndex() + 1);
1322                    circleDsp.addDisplayable(cd);
1323                }
1324                }
1325                addDisplayable(circleDsp, FLAG_COLORTABLE);
1326
1327        } catch (Exception e) {
1328            e.printStackTrace();
1329        }
1330    }
1331
1332    public void setAntColor(Color c) {
1333        if (c == null) c = defaultAntColor;
1334        try {
1335            antColor = c;
1336            circleDsp.setColor(c);
1337        } catch (Exception e) {
1338            logger.error("Exception in PolarOrbitTrackControl.setAntColor e=" + e);
1339        }
1340    }
1341        
1342    private void setSatelliteAltitude(double val) {
1343        satelliteAltitude = val;
1344    }
1345    
1346    public void setStation(String val) {
1347        station = val.trim();
1348    }
1349    
1350    private int validateSwathWidthField() {
1351        String s = swathWidthFld.getText().trim();
1352        int val = -1;
1353        try {
1354                val = Integer.parseInt(s);
1355        } catch (NumberFormatException nfe) {
1356                // throw up a dialog to tell user the problem
1357                JOptionPane.showMessageDialog(latLonAltPanel, 
1358                                "Invalid swath width: must be an integer value in km");
1359                return -1;
1360        }
1361
1362        if ((val < SWATH_WIDTH_MIN) || (val > SWATH_WIDTH_MAX)) {
1363                // throw up a dialog to tell user the problem
1364                JOptionPane.showMessageDialog(latLonAltPanel, 
1365                                "Swath width valid range is " + SWATH_WIDTH_MIN + 
1366                                " to " + SWATH_WIDTH_MAX + " km");
1367                return -1;
1368        }
1369        return val;
1370    }
1371    
1372}