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