001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2025 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 https://www.gnu.org/licenses/. 027 */ 028 029package edu.wisc.ssec.mcidasv.control; 030 031import static java.lang.Math.asin; 032import static java.lang.Math.atan2; 033import static java.lang.Math.cos; 034import static java.lang.Math.sin; 035import static java.lang.Math.sqrt; 036import static ucar.unidata.util.LayoutUtil.centerBottom; 037import static ucar.unidata.util.LayoutUtil.inset; 038import static visad.RealTupleType.LatitudeLongitudeTuple; 039import edu.wisc.ssec.mcidasv.Constants; 040import edu.wisc.ssec.mcidasv.McIdasPreferenceManager; 041import edu.wisc.ssec.mcidasv.data.GroundStation; 042import edu.wisc.ssec.mcidasv.data.GroundStations; 043import edu.wisc.ssec.mcidasv.data.PolarOrbitTrackDataSource; 044import edu.wisc.ssec.mcidasv.data.TimeRangeSelection; 045import edu.wisc.ssec.mcidasv.data.hydra.CurveDrawer; 046import edu.wisc.ssec.mcidasv.ui.ColorSwatchComponent; 047import edu.wisc.ssec.mcidasv.util.XmlUtil; 048 049import java.awt.Color; 050import java.awt.Container; 051import java.awt.Dimension; 052import java.awt.FlowLayout; 053import java.awt.Font; 054import java.awt.Window; 055import java.awt.event.ActionEvent; 056import java.awt.event.ActionListener; 057import java.awt.event.ItemEvent; 058import java.rmi.RemoteException; 059import java.util.ArrayList; 060import java.util.HashMap; 061import java.util.Hashtable; 062import java.util.List; 063import java.util.Map; 064import java.util.Objects; 065import java.util.regex.Pattern; 066 067import javax.swing.BorderFactory; 068import javax.swing.Box; 069import javax.swing.BoxLayout; 070import javax.swing.DefaultComboBoxModel; 071import javax.swing.JButton; 072import javax.swing.JCheckBox; 073import javax.swing.JComboBox; 074import javax.swing.JComponent; 075import javax.swing.JDialog; 076import javax.swing.JLabel; 077import javax.swing.JOptionPane; 078import javax.swing.JPanel; 079import javax.swing.JSpinner; 080import javax.swing.JTabbedPane; 081import javax.swing.JTextField; 082import javax.swing.SpinnerNumberModel; 083import javax.swing.SwingUtilities; 084import javax.swing.ToolTipManager; 085 086import name.gano.astro.AstroConst; 087import net.miginfocom.swing.MigLayout; 088 089import org.slf4j.Logger; 090import org.slf4j.LoggerFactory; 091import org.w3c.dom.Element; 092import org.w3c.dom.NodeList; 093 094import ucar.unidata.data.DataChoice; 095import ucar.unidata.data.DataSourceImpl; 096import ucar.unidata.idv.ControlContext; 097import ucar.unidata.idv.control.DisplayControlBase; 098import ucar.unidata.idv.control.DisplayControlImpl; 099import ucar.unidata.ui.FontSelector; 100import ucar.unidata.util.GuiUtils; 101import ucar.unidata.util.IOUtil; 102import ucar.unidata.util.Msg; 103import ucar.unidata.view.geoloc.NavigatedDisplay; 104import ucar.visad.UtcDate; 105import ucar.visad.Util; 106import ucar.visad.display.CompositeDisplayable; 107import ucar.visad.display.TextDisplayable; 108import visad.Data; 109import visad.DisplayRealType; 110import visad.Gridded2DSet; 111import visad.MathType; 112import visad.RealTuple; 113import visad.RealTupleType; 114import visad.SampledSet; 115import visad.Text; 116import visad.TextControl; 117import visad.TextType; 118import visad.Tuple; 119import visad.TupleType; 120import visad.UnionSet; 121import visad.VisADException; 122import visad.georef.EarthLocation; 123import visad.georef.EarthLocationTuple; 124import visad.georef.LatLonTuple; 125 126/** 127 * {@link DisplayControlImpl} with some McIDAS-V specific extensions. 128 * 129 * <p>Namely parameter sets and support for inverted parameter defaults.</p> 130 */ 131 132public class PolarOrbitTrackControl extends DisplayControlImpl { 133 134 private static final Logger logger = 135 LoggerFactory.getLogger(PolarOrbitTrackControl.class); 136 137 private static final String ERR_DIALOG_TITLE = "Time Range Selection Error"; 138 139 private static final Pattern REGEX = Pattern.compile(" "); 140 141 private JLabel satelliteName = new JLabel(""); 142 private static final JLabel kmLabel = new JLabel("km"); 143 private JTextField swathWidthFld = null; 144 private JPanel swathWidthPanel; 145 146 private double latitude; 147 private double longitude; 148 private JPanel fontSizePanel; 149 private JPanel colorPanel; 150 private JPanel antColorPanel; 151 private JPanel locationPanel; 152 private JPanel latLonAltPanel; 153 154 /** Property name to get the list or URLs */ 155 public final String PREF_GROUNDSTATIONS = "mcv.groundstations"; 156 157 private JComboBox<GroundStation> locationComboBox; 158 private JComboBox<GroundStation> jcbStationsPlotted; 159 160 private final List<GroundStation> stations = new ArrayList<>(); 161 162 private JComboBox<String> jcbTrackLineStyle = 163 new JComboBox<>(Constants.lineStyles); 164 165 private JComboBox<String> jcbEdgeLineStyle = 166 new JComboBox<>(Constants.lineStyles); 167 168 private JComboBox<String> jcbStationLineStyle = 169 new JComboBox<>(Constants.lineStyles); 170 171 private JCheckBox jcbLabels; 172 private JCheckBox jcbSwathEdges; 173 private boolean swathEdgesOn = false; 174 private String [] lineWidths = {"1", "2", "3", "4"}; 175 176 private JComboBox<String> jcbStationLineWidth = 177 new JComboBox<>(lineWidths); 178 179 private JComboBox<String> jcbSwathCenterLineWidth = 180 new JComboBox<>(lineWidths); 181 182 // names to distinguish checkbox event sources 183 private static final String CHECKBOX_LABELS = "CHECKBOX_LABELS"; 184 private static final String CHECKBOX_SWATH_EDGES = "CHECKBOX_SWATH_EDGES"; 185 186 private String station = ""; 187 188 private static final int SWATH_WIDTH_MIN = 0; 189 // swath width not applicable, e.g. GEO sensor 190 private static final String SWATH_NA = "N/A"; 191 // TJJ Feb 2014 - need to determine max of any sensor. VIIRS is over 3000 km 192 private static final int SWATH_WIDTH_MAX = 4000; 193 194 private static final int MAX_ANTENNA_ANGLE = 90; 195 private int curAngle = GroundStation.DEFAULT_ANTENNA_ANGLE; 196 private int curElevation = 0; 197 private static final double LABEL_DISTANCE_THRESHOLD = 2.5d; 198 199 // Valid range for custom ground station elevation 200 private static final int MIN_ELEVATION = -500; 201 private static final int MAX_ELEVATION = 8850; 202 203 private DataChoice dataChoice; 204 205 private JLabel latLabel; 206 private JLabel lonLabel; 207 private JLabel altLabel; 208 private JTextField antennaAngle = new JTextField("" + 209 GroundStation.DEFAULT_ANTENNA_ANGLE, GroundStation.DEFAULT_ANTENNA_ANGLE); 210 211 // custom ground station UI components 212 JTextField customLat = null; 213 JTextField customLon = null; 214 JTextField customLab = null; 215 JTextField customAlt = null; 216 217 /** the font selectors, Orbit Track (ot) and Ground Station (gs) */ 218 private FontSelector otFontSelector; 219 private Font otCurFont = null; 220 private int otCurFontSize = -1; 221 private FontSelector gsFontSelector; 222 223 // line width combo boxes, Station: Ground Station, SC: Swath Center, SE: Swath Edge 224 private JComboBox<String> jcbSwathEdgeLineWidth = 225 new JComboBox<>(lineWidths); 226 227 private JSpinner js = null; 228 229 private CompositeDisplayable trackDsp; 230 private CompositeDisplayable timeLabelDsp; 231 private CompositeDisplayable swathEdgeDsp; 232 233 // time label variables 234 private static final int DEFAULT_LABEL_INTERVAL = 5; 235 private int curLabelInterval = DEFAULT_LABEL_INTERVAL; 236 private int prvLabelInterval = DEFAULT_LABEL_INTERVAL; 237 238 private ColorSwatchComponent colorSwatch; 239 240 private static final Color DEFAULT_COLOR = Color.GREEN; 241 private Color curSwathColor = null; 242 private Color prvSwathColor = DEFAULT_COLOR; 243 244 private ColorSwatchComponent antColorSwatch; 245 private Color antColor; 246 private Color defaultAntColor = Color.MAGENTA; 247 private PolarOrbitTrackDataSource dataSource; 248 249 private double trackZ = 0.0d; 250 private double gsZ = 0.0d; 251 private NavigatedDisplay navDsp = null; 252 private TextType otTextType = null; 253 private static long ttCounter = 0; 254 private double curWidth = 0.0d; 255 private double prvWidth = 0.0d; 256 257 private int prvTrackLineStyle = 0; 258 private int prvEdgeLineStyle = 1; 259 private int curTrackLineStyle = 0; 260 private int curEdgeLineStyle = 1; 261 private static final float FONT_SCALE_FACTOR = 12.0f; 262 263 // line width for drawing track center and swath edges 264 private int prvSwathCenterWidth = 2; 265 private int curSwathCenterWidth = 2; 266 private int prvSwathEdgeWidth = 1; 267 private int curSwathEdgeWidth = 1; 268 269 /** Path to the McV swathwidths.xml */ 270 private static final String SWATH_WIDTHS = "/edu/wisc/ssec/mcidasv/resources/swathwidths.xml"; 271 private static final String TAG_SATELLITE = "satellite"; 272 private static final String ATTR_NAME = "name"; 273 private static final String ATTR_WIDTH = "width"; 274 275 private static final String SWATH_MODS = "OrbitTrack"; 276 private static final String STATION_MODS = "GroundStation"; 277 private static final String STATION_ADD = "AddStation"; 278 private static final String STATION_REM = "RemStation"; 279 private static final String CUSTOM_ADD = "AddCustom"; 280 private static final String ACTIVE_STATION = "ActiveStation"; 281 282 // Constants for the various UI tool-tips 283 private static final String TOOLTIP_ADD_CUSTOM = 284 "Station will be plotted with Color, Font, and Line Width/Style options currently selected"; 285 private static final String TOOLTIP_ADD_SELECTED = 286 "Station will be plotted with Color, Font, and Line Width/Style options currently selected"; 287 private static final String TOOLTIP_ANTENNA_ANGLE = 288 "Antenna elevation angle, valid range 5 to 90 degrees"; 289 private static final String TOOLTIP_CUSTOM_ALT = 290 "Antenna altitude valid range: " + MIN_ELEVATION + " m to: " + MAX_ELEVATION + " m"; 291 private static final String TOOLTIP_CUSTOM_LABEL = 292 "Choose a label, e.g. \"Mesa, AZ\""; 293 private static final String TOOLTIP_CUSTOM_LAT = 294 "Latitude of your custom groundstation"; 295 private static final String TOOLTIP_CUSTOM_LON = 296 "Longitude of your custom groundstation"; 297 private static final String TOOLTIP_SWATH_WIDTH = 298 "Valid range: > " + SWATH_WIDTH_MIN + " km to: " + SWATH_WIDTH_MAX + " km"; 299 private static final String TOOLTIP_LABEL_INTERVAL = 300 "Interval in minutes between orbit track time labels"; 301 302 private final Map<GroundStation, TextDisplayable> stationToText = 303 new HashMap<>(); 304 305 private final Map<GroundStation, CurveDrawer> stationToCurve = 306 new HashMap<>(); 307 308 // Used in showProperties override to help decide if we need to redraw 309 Hashtable<String, Object> oldProps = null; 310 311 private Element root = null; 312 313 private boolean showingLabels; 314 315 // initial scale for labeling 316 float scale = 1.0f; 317 318 public PolarOrbitTrackControl() { 319 logger.trace("created new PolarOrbitTrackControl..."); 320 setHelpUrl("idv.control.orbittrackcontrol"); 321 try { 322 final String xml = 323 IOUtil.readContents(SWATH_WIDTHS, McIdasPreferenceManager.class); 324 root = XmlUtil.getRoot(xml); 325 if (curSwathColor == null) curSwathColor = DEFAULT_COLOR; 326 } catch (Exception e) { 327 logger.error("problem reading swathwidths.xml", e); 328 } 329 } 330 331 /** 332 * Get the DisplayListTemplate property. This method is a fair bit different from its parent, 333 * in order to allow overrides to utilize the default display list template method. 334 * TJJ Jun 2023 - See https://mcidas.ssec.wisc.edu/inquiry-v/?inquiry=2772 335 * 336 * @return The DisplayListTemplate 337 */ 338 public String getDisplayListTemplate() { 339 if (displayListTemplate == null) { 340 String pref = PREF_DISPLAYLIST_TEMPLATE + '.' + displayId; 341 boolean haveData = (getShortParamName() != null); 342 pref = pref + (haveData ? ".data" : ".nodata"); 343 displayListTemplate = getStore().get(pref, getDefaultDisplayListTemplate()); 344 } 345 return displayListTemplate; 346 } 347 348 /** 349 * Override because the base class template results in a very long layer label. 350 * TJJ Jun 2023 - See https://mcidas.ssec.wisc.edu/inquiry-v/?inquiry=2772 351 * 352 * @return The DefaultDisplayListTemplate 353 */ 354 @Override protected String getDefaultDisplayListTemplate() { 355 return (getShortParamName() != null) // haveData 356 ? MACRO_DISPLAYNAME + " - " + MACRO_TIMESTAMP : MACRO_DISPLAYNAME; 357 } 358 359 /** 360 * Deal with action events 361 * 362 * @param ae the ActionEvent fired when the user applies changes 363 */ 364 365 public void actionPerformed(ActionEvent ae) { 366 367 // user trying to add a custom ground station 368 if (CUSTOM_ADD.equals(ae.getActionCommand())) { 369 370 logger.debug("Custom Ground Station..."); 371 String labStr = customLab.getText(); 372 if ((labStr == null) || (labStr.isEmpty())) { 373 JOptionPane.showMessageDialog(null, 374 "Please provide a label for the custom ground station."); 375 return; 376 } 377 float fLat; 378 float fLon; 379 try { 380 fLat = Float.parseFloat(customLat.getText()); 381 fLon = Float.parseFloat(customLon.getText()); 382 } catch (NumberFormatException nfe) { 383 JOptionPane.showMessageDialog(null, 384 "Latitude and Longitude must be floating point numbers, please correct."); 385 return; 386 } 387 if ((fLat < -90) || (fLat > 90)) { 388 JOptionPane.showMessageDialog(null, 389 "Latitude is out of valid range: " + fLat); 390 return; 391 } 392 if ((fLon < -180) || (fLon > 180)) { 393 JOptionPane.showMessageDialog(null, 394 "Longitude is out of valid range: " + fLon); 395 return; 396 } 397 398 // Validate the elevation 399 String s = customAlt.getText(); 400 try { 401 int newElevation = Integer.parseInt(s); 402 if (newElevation != curElevation) { 403 // Always need to do range check too 404 if ((newElevation < MIN_ELEVATION) || 405 (newElevation > MAX_ELEVATION)) { 406 throw new NumberFormatException(); 407 } 408 curElevation = newElevation; 409 } 410 } catch (NumberFormatException nfe) { 411 JOptionPane.showMessageDialog(null, 412 TOOLTIP_CUSTOM_ALT); 413 return; 414 } 415 416 // Validate the antenna angle 417 s = antennaAngle.getText(); 418 try { 419 int newAngle = Integer.parseInt(s); 420 if (newAngle != curAngle) { 421 // Always need to do range check too 422 if ((newAngle < GroundStation.DEFAULT_ANTENNA_ANGLE) || 423 (newAngle > MAX_ANTENNA_ANGLE)) { 424 throw new NumberFormatException(); 425 } 426 curAngle = newAngle; 427 } 428 } catch (NumberFormatException nfe) { 429 JOptionPane.showMessageDialog(null, 430 "Invalid antenna angle: " + s); 431 return; 432 } 433 434 // last check, is this label already used? 435 int numPlotted = jcbStationsPlotted.getItemCount(); 436 for (int i = 0; i < numPlotted; i++) { 437 GroundStation gs = jcbStationsPlotted.getItemAt(i); 438 if ((gs.getName() != null) && gs.getName().equals(station)) { 439 JOptionPane.showMessageDialog(null, 440 "A station with this label has already been plotted: " + station); 441 return; 442 } 443 } 444 445 // if we made it this far, fields are valid, we can create a custom ground station 446 // create new earth location, add it to stations plotted, set index, 447 448 // update scale in case user changed zoom level 449 scale = getViewManager().getMaster().getDisplayScale(); 450 451 // make an Earth location 452 EarthLocationTuple elt = null; 453 try { 454 elt = new EarthLocationTuple(fLat, fLon, curElevation); 455 } catch (VisADException | RemoteException e) { 456 logger.error("Problem creating EarthLocationTuple", e); 457 } 458 459 double satelliteAltitude = dataSource.getNearestAltToGroundStation(latitude, longitude) / 1000.0; 460 GroundStation gs = new GroundStation(labStr, elt, curAngle, satelliteAltitude); 461 addGroundStation(gs, true); 462 jcbStationsPlotted.addItem(gs); 463 jcbStationsPlotted.setSelectedItem(gs); 464 updateDisplayList(); 465 return; 466 } 467 468 // user trying to add a new ground station to those plotted on display 469 if (STATION_ADD.equals(ae.getActionCommand())) { 470 logger.debug("Add Station..."); 471 GroundStation addedStation = 472 (GroundStation) locationComboBox.getSelectedItem(); 473 474 boolean alreadyPlotted = false; 475 int numPlotted = jcbStationsPlotted.getItemCount(); 476 for (int i = 0; i < numPlotted; i++) { 477 GroundStation gs = jcbStationsPlotted.getItemAt(i); 478 if (Objects.equals(gs.getName(), addedStation.getName())) { 479 alreadyPlotted = true; 480 break; 481 } 482 } 483 if (alreadyPlotted) { 484 JOptionPane.showMessageDialog(null, 485 "Station already plotted on display: " + addedStation); 486 return; 487 } else { 488 // Validate the antenna angle - only piece that can have errors for "stock" stations 489 String s = antennaAngle.getText(); 490 try { 491 int newAngle = Integer.parseInt(s); 492 if (newAngle != curAngle) { 493 // Always need to do range check too 494 if ((newAngle < GroundStation.DEFAULT_ANTENNA_ANGLE) || 495 (newAngle > MAX_ANTENNA_ANGLE)) { 496 throw new NumberFormatException(); 497 } 498 curAngle = newAngle; 499 addedStation.setAntennaAngle(curAngle); 500 } 501 } catch (NumberFormatException nfe) { 502 JOptionPane.showMessageDialog(null, 503 "Invalid antenna angle: " + s); 504 return; 505 } 506 addGroundStation(addedStation, false); 507 jcbStationsPlotted.addItem(addedStation); 508 jcbStationsPlotted.setSelectedItem(addedStation); 509 510 } 511 updateDisplayList(); 512 return; 513 } 514 515 // Active station changed (selection from plotted stations) 516 if (ACTIVE_STATION.equals(ae.getActionCommand())) { 517 logger.debug("Active Station changed..."); 518 GroundStation gs = (GroundStation) jcbStationsPlotted.getSelectedItem(); 519 if (gs == null) { 520 JOptionPane.showMessageDialog(null, 521 "No Active Stations"); 522 } else { 523 // Update UI with settings for this station 524 updateGroundStationWidgets(gs); 525 } 526 return; 527 } 528 529 // user removing a ground station from the display 530 if (STATION_REM.equals(ae.getActionCommand())) { 531 logger.debug("Rem Station..."); 532 GroundStation gs = (GroundStation) jcbStationsPlotted.getSelectedItem(); 533 if (gs == null) { 534 JOptionPane.showMessageDialog(null, 535 "Nothing to remove"); 536 } else { 537 try { 538 removeDisplayable(stationToCurve.get(gs)); 539 removeDisplayable(stationToText.get(gs)); 540 } catch (RemoteException | VisADException e) { 541 logger.error("Problem removing displayables", e); 542 } 543 544 jcbStationsPlotted.removeItem(gs); 545 // Did we remove the last active station? 546 if (jcbStationsPlotted.getItemCount() == 0) { 547 latLabel.setText(" - "); 548 lonLabel.setText(" - "); 549 altLabel.setText(" - "); 550 } 551 552 } 553 updateDisplayList(); 554 return; 555 } 556 557 // swath-related changes 558 if (SWATH_MODS.equals(ae.getActionCommand())) { 559 logger.debug("Apply Swath Mods..."); 560 561 boolean fontChanged = false; 562 boolean swathChanged = false; 563 scale = getViewManager().getMaster().getDisplayScale(); 564 565 curSwathCenterWidth = jcbSwathCenterLineWidth.getSelectedIndex() + 1; 566 if (curSwathCenterWidth != prvSwathCenterWidth) { 567 prvSwathCenterWidth = curSwathCenterWidth; 568 swathChanged = true; 569 } 570 571 curSwathEdgeWidth = jcbSwathEdgeLineWidth.getSelectedIndex() + 1; 572 if (curSwathEdgeWidth != prvSwathEdgeWidth) { 573 prvSwathEdgeWidth = curSwathEdgeWidth; 574 swathChanged = true; 575 } 576 577 curTrackLineStyle = jcbTrackLineStyle.getSelectedIndex(); 578 if (curTrackLineStyle != prvTrackLineStyle) { 579 prvTrackLineStyle = curTrackLineStyle; 580 swathChanged = true; 581 } 582 583 curEdgeLineStyle = jcbEdgeLineStyle.getSelectedIndex(); 584 if (curEdgeLineStyle != prvEdgeLineStyle) { 585 prvEdgeLineStyle = curEdgeLineStyle; 586 swathChanged = true; 587 } 588 589 curSwathColor = colorSwatch.getColor(); 590 if (! curSwathColor.equals(prvSwathColor)) { 591 prvSwathColor = curSwathColor; 592 swathChanged = true; 593 } 594 595 float newSwathWidth = validateSwathWidthField(); 596 if (newSwathWidth > 0) { 597 curWidth = newSwathWidth; 598 if (Double.compare(curWidth, prvWidth) != 0) { 599 prvWidth = curWidth; 600 swathChanged = true; 601 } 602 } else { 603 // Don't apply anything if there are "errors on the form" 604 if (newSwathWidth == -1) return; 605 } 606 607 // update font attributes if necessary 608 Font f = otFontSelector.getFont(); 609 if (! f.equals(otCurFont)) { 610 otCurFont = f; 611 fontChanged = true; 612 } 613 if (f.getSize() != otCurFontSize) { 614 otCurFontSize = f.getSize(); 615 fontChanged = true; 616 } 617 618 // see if label interval has changed 619 SpinnerNumberModel snm = (SpinnerNumberModel) (js.getModel()); 620 prvLabelInterval = ((Integer) snm.getValue()).intValue(); 621 if ((prvLabelInterval != curLabelInterval) || fontChanged) { 622 curLabelInterval = prvLabelInterval; 623 swathChanged = true; 624 } 625 626 // check swath width field, update if necessary 627 if (swathChanged || fontChanged) redrawAll(); 628 updateDisplayList(); 629 return; 630 } 631 632 // Ground station mods 633 if (STATION_MODS.equals(ae.getActionCommand())) { 634 635 GroundStation gs = (GroundStation) jcbStationsPlotted.getSelectedItem(); 636 if (gs == null) { 637 // No stations plotted, nothing to do 638 JOptionPane.showMessageDialog(null, 639 "No stations plotted, nothing to apply."); 640 return; 641 } 642 643 logger.debug("Apply Station mods for: {}, cur font name: {}, cur font size: {}, cur color: {}", 644 gs.getName(), 645 gs.getFont().getFontName(), 646 gs.getFont().getSize(), 647 gs.getColor()); 648 649 // flag indicates user changed some parameter 650 boolean somethingChanged = false; 651 652 // Check each parameter of the ground station selected with UI settings 653 654 // Color 655 if (stationToText.get(gs).getColor() != antColorSwatch.getColor()) { 656 logger.debug("GroundStation color change..."); 657 try { 658 updateStationColor(gs, antColorSwatch.getColor()); 659 } catch (RemoteException | VisADException e) { 660 logger.error("Problem changing ground station color", e); 661 } 662 somethingChanged = true; 663 } 664 665 // Font 666 if (stationToText.get(gs).getFont() != gsFontSelector.getFont()) { 667 logger.debug("GroundStation font change..."); 668 try { 669 updateStationFont(gs, gsFontSelector.getFont()); 670 } catch (RemoteException | VisADException e) { 671 logger.error("Problem changing ground station font", e); 672 } 673 somethingChanged = true; 674 } 675 676 // Antenna angle 677 // If this changes, need to create a new CurveDrawer object 678 679 String s = antennaAngle.getText(); 680 curAngle = gs.getAntennaAngle(); 681 try { 682 int newAngle = Integer.parseInt(s); 683 if (newAngle != curAngle) { 684 // TJJ Jun 2015 range check 685 if ((newAngle < GroundStation.DEFAULT_ANTENNA_ANGLE) || 686 (newAngle > MAX_ANTENNA_ANGLE)) { 687 throw new NumberFormatException(); 688 } else { 689 logger.debug("GroundStation antenna angle change..."); 690 try { 691 removeDisplayable(stationToCurve.get(gs)); 692 removeDisplayable(stationToText.get(gs)); 693 } catch (RemoteException | VisADException e) { 694 logger.error("Problem removing displayable", e); 695 } 696 gs.setAntennaAngle(newAngle); 697 gs.setColor(antColorSwatch.getColor()); 698 CurveDrawer cdNew = makeCoverageCircle(gs); 699 addDisplayable(cdNew); 700 addDisplayable(stationToText.get(gs)); 701 stationToCurve.put(gs, cdNew); 702 curAngle = newAngle; 703 somethingChanged = true; 704 } 705 } 706 } catch (NumberFormatException nfe) { 707 JOptionPane.showMessageDialog(latLonAltPanel, 708 "Antenna angle valid range is " + GroundStation.DEFAULT_ANTENNA_ANGLE + 709 " to " + MAX_ANTENNA_ANGLE + " degrees"); 710 return; 711 } 712 713 // Line style and width 714 715 CurveDrawer cd = stationToCurve.get(gs); 716 int cdWidth = (int) cd.getLineWidth(); 717 int curStyle = cd.getLineStyle(); 718 719 if (cdWidth != (jcbStationLineWidth.getSelectedIndex() + 1)) { 720 try { 721 logger.debug("GroundStation line width change..."); 722 gs.setColor(antColorSwatch.getColor()); 723 replaceCurve(gs); 724 } catch (RemoteException | VisADException e) { 725 logger.error("Problem changing ground station line width", e); 726 } 727 somethingChanged = true; 728 } 729 730 if (curStyle != (jcbStationLineStyle.getSelectedIndex())) { 731 try { 732 logger.debug("GroundStation line style change..."); 733 gs.setColor(antColorSwatch.getColor()); 734 replaceCurve(gs); 735 } catch (RemoteException | VisADException e) { 736 logger.error("Problem changing ground station line style", e); 737 } 738 somethingChanged = true; 739 } 740 741 if (somethingChanged) { 742 updateDisplayList(); 743 } 744 return; 745 } 746 } 747 748 /** 749 * Apply the map (height) position to the displays 750 */ 751 752 private void applyDisplayableLevels() { 753 try { 754 DisplayRealType dispType = navDsp.getDisplayAltitudeType(); 755 trackDsp.setConstantPosition(trackZ, dispType); 756 timeLabelDsp.setConstantPosition(trackZ, dispType); 757 swathEdgeDsp.setConstantPosition(trackZ, dispType); 758 applyProperties(); 759 } catch (Exception e) { 760 logger.error("Problem applying displayable levels", e); 761 } 762 } 763 764 // No explicit dimension changes, but for times we need to redraw 765 // everything due to combinations of zooming and visibility toggling 766 private void redrawAll() { 767 logger.debug("redrawAll() in..."); 768 try { 769 removeDisplayable(swathEdgeDsp); 770 removeDisplayable(trackDsp); 771 removeDisplayable(timeLabelDsp); 772 swathEdgeDsp = null; 773 trackDsp = null; 774 timeLabelDsp = null; 775 Data data = getData(getDataInstance()); 776 swathEdgeDsp = new CompositeDisplayable(); 777 trackDsp = new CompositeDisplayable(); 778 timeLabelDsp = new CompositeDisplayable(); 779 // turn visibility off for those elements which have checkboxes for this 780 if (! jcbSwathEdges.isSelected()) swathEdgeDsp.setVisible(false); 781 if (! jcbLabels.isSelected()) timeLabelDsp.setVisible(false); 782 createTrackDisplay(data, true); 783 applyDisplayableLevels(); 784 } catch (Exception e) { 785 logger.error("Problem redrawing", e); 786 } 787 } 788 789 private void createTrackDisplay(Data data, boolean doTrack) { 790 791 logger.debug("createTrackDisplay() in..."); 792 // Always check for View scale change (user zoomed in or out) 793 scale = getViewManager().getMaster().getDisplayScale(); 794 try { 795 List<String> dts = new ArrayList<>(); 796 if (data instanceof Tuple) { 797 Data[] dataArr = ((Tuple) data).getComponents(); 798 799 int npts = dataArr.length; 800 float[][] latlon = new float[2][npts]; 801 double distance = 0.0d; 802 LatLonTuple prvPoint = null; 803 804 for (int i = 0; i < npts; i++) { 805 Tuple t = (Tuple) dataArr[i]; 806 Data[] tupleComps = t.getComponents(); 807 808 LatLonTuple llt = (LatLonTuple) tupleComps[1]; 809 double dlat = llt.getLatitude().getValue(); 810 double dlon = llt.getLongitude().getValue(); 811 812 if (doTrack) { 813 if ((i % curLabelInterval) == 0) { 814 815 if (prvPoint != null) { 816 distance = Util.distance(prvPoint, llt); 817 if (distance < LABEL_DISTANCE_THRESHOLD) { 818 latlon[0][i] = (float) dlat; 819 latlon[1][i] = (float) dlon; 820 continue; 821 } 822 } 823 824 String str = ((Text) tupleComps[0]).getValue(); 825 dts.add(str); 826 int indx = str.indexOf(' ') + 1; 827 String subStr = "- " + str.substring(indx, indx + 5); 828 TextDisplayable time = new TextDisplayable(SWATH_MODS, otTextType); 829 time.setJustification(TextControl.Justification.LEFT); 830 time.setVerticalJustification(TextControl.Justification.CENTER); 831 time.setColor(curSwathColor); 832 time.setTextSize((float) scale * otFontSelector.getFontSize() / FONT_SCALE_FACTOR); 833 time.setFont(otFontSelector.getFont()); 834 time.setSphere(inGlobeDisplay()); 835 time.setUseFastRendering(false); 836 837 RealTuple lonLat = 838 new RealTuple(RealTupleType.SpatialEarth2DTuple, 839 new double[] { dlon, dlat }); 840 Tuple tup = new Tuple(makeTupleType(otTextType), 841 new Data[] { lonLat, new Text(otTextType, subStr)}); 842 time.setData(tup); 843 if (jcbLabels.isSelected()) timeLabelDsp.addDisplayable(time); 844 845 prvPoint = llt; 846 } 847 } 848 latlon[0][i] = (float) dlat; 849 latlon[1][i] = (float) dlon; 850 } 851 852 if (doTrack) { 853 drawSwathLine(latlon, 854 npts, 855 jcbTrackLineStyle.getSelectedIndex(), 856 trackDsp, 857 curSwathColor, 858 curSwathCenterWidth); 859 860 addDisplayable(trackDsp); 861 addDisplayable(timeLabelDsp); 862 } 863 864 // We initialize swath edge objects whenever possible, we just show or 865 // hide them based on checkbox state 866 if (curWidth > 0) { 867 float[][][] crv = getSwath(latlon); 868 int npt = crv[0][0].length; 869 float[][] leftC = new float[2][npt]; 870 float[][] rightC = new float[2][npt]; 871 for (int i = 0; i < npt; i++) { 872 leftC[0][i] = crv[0][0][i]; 873 leftC[1][i] = crv[0][1][i]; 874 rightC[0][i] = crv[1][0][i]; 875 rightC[1][i] = crv[1][1][i]; 876 } 877 878 drawSwathLine(leftC, 879 npt, 880 jcbEdgeLineStyle.getSelectedIndex(), 881 swathEdgeDsp, 882 curSwathColor, 883 curSwathEdgeWidth); 884 885 drawSwathLine(rightC, 886 npt, 887 jcbEdgeLineStyle.getSelectedIndex(), 888 swathEdgeDsp, 889 curSwathColor, 890 curSwathEdgeWidth); 891 892 if (! jcbSwathEdges.isSelected()) { 893 swathEdgeDsp.setVisible(false); 894 } 895 addDisplayable(swathEdgeDsp); 896 } 897 } 898 } catch (Exception e) { 899 logger.error("Problem creating track display", e); 900 } 901 } 902 903 private static void drawSwathLine(float[][] points, 904 int pointCount, 905 int lineStyle, 906 CompositeDisplayable displayable, 907 Color color, 908 int width) 909 throws VisADException, RemoteException 910 { 911 Gridded2DSet g2dset = new Gridded2DSet(LatitudeLongitudeTuple, 912 points, 913 pointCount); 914 SampledSet[] sampledSets = new SampledSet[] { g2dset }; 915 UnionSet unionSet = new UnionSet(sampledSets); 916 CurveDrawer lines = new CurveDrawer(unionSet); 917 lines.setLineStyle(lineStyle); 918 lines.setData(unionSet); 919 lines.setDrawingEnabled(false); 920 displayable.addDisplayable(lines); 921 displayable.setColor(color); 922 displayable.setLineWidth(width); 923 } 924 925 /* (non-Javadoc) 926 * @see ucar.unidata.idv.control.DisplayControlImpl#displayableToFront() 927 */ 928 @Override 929 public void displayableToFront() { 930 redrawAll(); 931 } 932 933 /** 934 * Called by doMakeWindow in DisplayControlImpl, which then calls its 935 * doMakeMainButtonPanel(), which makes more buttons. 936 * 937 * @return container of contents 938 */ 939 940 public Container doMakeContents() { 941 942 fontSizePanel = new JPanel(); 943 fontSizePanel.setLayout(new BoxLayout(fontSizePanel, BoxLayout.Y_AXIS)); 944 JPanel labelPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 945 labelPanel.add(jcbLabels); 946 947 // same row, add label interval spinner 948 Integer defaultInterval = 5; 949 Integer minInterval = 1; 950 Integer maxInterval = 120; 951 Integer intervalStep = 1; 952 SpinnerNumberModel snm = 953 new SpinnerNumberModel(defaultInterval, minInterval, maxInterval, intervalStep); 954 JLabel intervalLabel = new JLabel("Label Interval:"); 955 JLabel intervalUnits = new JLabel("minutes"); 956 js = new JSpinner(snm); 957 js.setToolTipText(TOOLTIP_LABEL_INTERVAL); 958 labelPanel.add(Box.createHorizontalStrut(5)); 959 labelPanel.add(intervalLabel); 960 labelPanel.add(js); 961 labelPanel.add(intervalUnits); 962 963 // line style for drawing swath track and width edges 964 jcbTrackLineStyle.addActionListener(this); 965 // will init to default of solid 966 jcbTrackLineStyle.setSelectedIndex(curTrackLineStyle); 967 968 // Swath center line width 969 jcbSwathCenterLineWidth.addActionListener(this); 970 jcbSwathCenterLineWidth.setSelectedIndex(curSwathCenterWidth - 1); 971 972 // Swath edge line width 973 jcbSwathEdgeLineWidth.addActionListener(this); 974 jcbSwathEdgeLineWidth.setSelectedIndex(curSwathEdgeWidth - 1); 975 976 jcbEdgeLineStyle.addActionListener(this); 977 // will init to default of dashed 978 jcbEdgeLineStyle.setSelectedIndex(curEdgeLineStyle); 979 980 fontSizePanel.add(labelPanel); 981 JPanel botPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 982 botPanel.add(new JLabel("Font: ")); 983 botPanel.add(otFontSelector.getComponent()); 984 fontSizePanel.add(botPanel); 985 986 colorSwatch = new ColorSwatchComponent(getStore(), curSwathColor, "Color"); 987 colorSwatch.setPreferredSize(Constants.DEFAULT_COLOR_PICKER_SIZE); 988 989 colorPanel = new JPanel(new FlowLayout(FlowLayout.LEADING)); 990 colorPanel.add(new JLabel("Color: ")); 991 colorPanel.add(colorSwatch); 992 993 colorPanel.add(Box.createHorizontalStrut(5)); 994 colorPanel.add(new JLabel("Track Width: ")); 995 colorPanel.add(jcbSwathCenterLineWidth); 996 997 colorPanel.add(Box.createHorizontalStrut(4)); 998 colorPanel.add(new JLabel("Track Style: ")); 999 colorPanel.add(jcbTrackLineStyle); 1000 1001 colorPanel.add(Box.createHorizontalStrut(5)); 1002 colorPanel.add(new JLabel("Edge Width: ")); 1003 colorPanel.add(jcbSwathEdgeLineWidth); 1004 1005 colorPanel.add(Box.createHorizontalStrut(4)); 1006 colorPanel.add(new JLabel("Edge Style: ")); 1007 colorPanel.add(jcbEdgeLineStyle); 1008 1009 JPanel groundStationPanel = makeGroundStationPanel(); 1010 1011 swathWidthPanel = makeSwathWidthPanel(); 1012 1013 JPanel outerPanel = new JPanel(new MigLayout()); 1014 1015 JPanel mainPanel = new JPanel(new MigLayout()); 1016 mainPanel.setBorder(BorderFactory.createTitledBorder(" Swath Controls ")); 1017 mainPanel.add(swathWidthPanel, "wrap"); 1018 mainPanel.add(fontSizePanel, "wrap"); 1019 mainPanel.add(colorPanel, "wrap"); 1020 mainPanel.add(makeBottomRow(SWATH_MODS)); 1021 outerPanel.add(mainPanel, "wrap"); 1022 outerPanel.add(groundStationPanel, "wrap"); 1023 return outerPanel; 1024 } 1025 1026 private CurveDrawer makeCoverageCircle(GroundStation gs) { 1027 double lat = Math.toRadians(gs.getElt().getLatitude().getValue()); 1028 double lon = Math.toRadians(gs.getElt().getLongitude().getValue()); 1029 double satelliteAltitude = gs.getAltitude(); 1030 double elevation = gs.getElt().getAltitude().getValue(); 1031 1032 /* mean Earth radius in km */ 1033 double earthRadius = AstroConst.R_Earth_mean / 1000.0; 1034 // total radius to satellite 1035 satelliteAltitude += earthRadius; 1036 double SAC = (Math.PI / 2.0) + Math.toRadians(gs.getAntennaAngle()); 1037 // now accounts for station elevation - don't forget to convert from meters to km 1038 double sinASC = (earthRadius * sin(SAC)) / (satelliteAltitude - (elevation / 1000.0d)); 1039 double dist = earthRadius * (Math.PI - SAC - asin(sinASC)); 1040 double rat = dist / earthRadius; 1041 1042 // 360 degrees +1 points so we connect final segment, last point to first 1043 int npts = 361; 1044 float[][] latlon = new float[2][npts]; 1045 double cosDist = cos(rat); 1046 double sinDist = sin(rat); 1047 double sinLat = sin(lat); 1048 double cosLat = cos(lat); 1049 double sinLon = -sin(lon); 1050 double cosLon = cos(lon); 1051 for (int i = 0; i < npts; i++) { 1052 double azimuth = Math.toRadians((double) i); 1053 double cosBear = cos(azimuth); 1054 double sinBear = sin(azimuth); 1055 1056 double z = (cosDist * sinLat) + (sinDist * cosLat * cosBear); 1057 1058 double y = (cosLat * cosLon * cosDist) + 1059 (sinDist * ((sinLon * sinBear) - (sinLat * cosLon * cosBear))); 1060 1061 double x = (cosLat * sinLon * cosDist) - 1062 (sinDist * ((cosLon * sinBear) + (sinLat * sinLon * cosBear))); 1063 1064 double r = sqrt((x * x) + (y * y)); 1065 1066 double latRad = atan2(z, r); 1067 double lonRad = 0.0; 1068 if (r > 0.0) lonRad = -atan2(x, y); 1069 latlon[0][i] = (float) Math.toDegrees(latRad); 1070 latlon[1][i] = (float) Math.toDegrees(lonRad); 1071 } 1072 1073 CurveDrawer coverageCircle = null; 1074 try { 1075 Gridded2DSet circle = new Gridded2DSet(LatitudeLongitudeTuple, 1076 latlon, npts); 1077 SampledSet[] set = new SampledSet[1]; 1078 set[0] = circle; 1079 UnionSet uset = new UnionSet(set); 1080 coverageCircle = new CurveDrawer(uset); 1081 coverageCircle.setLineWidth(gs.getLineWidth()); 1082 coverageCircle.setLineStyle(gs.getLineStyle()); 1083 coverageCircle.setColor(gs.getColor()); 1084 coverageCircle.setData(uset); 1085 coverageCircle.setDrawingEnabled(false); 1086 if (! inGlobeDisplay()) { 1087 coverageCircle.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType()); 1088 } 1089 } catch (Exception e) { 1090 logger.error("Problem creating coverage circle", e); 1091 } 1092 stationToCurve.put(gs, coverageCircle); 1093 return coverageCircle; 1094 } 1095 1096 public Color getAntColor() { 1097 if (antColor == null) antColor = defaultAntColor; 1098 return antColor; 1099 } 1100 1101 /** 1102 * @return the curEdgeLineStyle 1103 */ 1104 public int getCurEdgeLineStyle() { 1105 return curEdgeLineStyle; 1106 } 1107 1108 /** 1109 * @param curEdgeLineStyle the curEdgeLineStyle to set 1110 */ 1111 public void setCurEdgeLineStyle(int curEdgeLineStyle) { 1112 this.curEdgeLineStyle = curEdgeLineStyle; 1113 } 1114 1115 /** 1116 * @return the curLabelInterval 1117 */ 1118 public int getCurLabelInterval() { 1119 return curLabelInterval; 1120 } 1121 1122 /** 1123 * @param curLabelInterval the curLabelInterval to set 1124 */ 1125 public void setCurLabelInterval(int curLabelInterval) { 1126 this.curLabelInterval = curLabelInterval; 1127 } 1128 1129 /** 1130 * @return the curSwathCenterWidth 1131 */ 1132 public int getCurSwathCenterWidth() { 1133 return curSwathCenterWidth; 1134 } 1135 1136 /** 1137 * @param curSwathCenterWidth the curSwathCenterWidth to set 1138 */ 1139 public void setCurSwathCenterWidth(int curSwathCenterWidth) { 1140 this.curSwathCenterWidth = curSwathCenterWidth; 1141 } 1142 1143 /** 1144 * @return the curSwathEdgeWidth 1145 */ 1146 public int getCurSwathEdgeWidth() { 1147 return curSwathEdgeWidth; 1148 } 1149 1150 /** 1151 * @param curSwathEdgeWidth the curSwathEdgeWidth to set 1152 */ 1153 public void setCurSwathEdgeWidth(int curSwathEdgeWidth) { 1154 this.curSwathEdgeWidth = curSwathEdgeWidth; 1155 } 1156 1157 /** 1158 * @return the curSwathColor 1159 */ 1160 public Color getCurSwathColor() { 1161 return curSwathColor; 1162 } 1163 1164 /** 1165 * @param curSwathColor the curSwathColor to set 1166 */ 1167 public void setCurSwathColor(Color curSwathColor) { 1168 this.curSwathColor = curSwathColor; 1169 } 1170 1171 /** 1172 * @return the curTrackLineStyle 1173 */ 1174 public int getCurTrackLineStyle() { 1175 return curTrackLineStyle; 1176 } 1177 1178 /** 1179 * @param curTrackLineStyle the curTrackLineStyle to set 1180 */ 1181 public void setCurTrackLineStyle(int curTrackLineStyle) { 1182 this.curTrackLineStyle = curTrackLineStyle; 1183 } 1184 1185 /** 1186 * @return the curWidth 1187 */ 1188 public double getCurWidth() { 1189 return curWidth; 1190 } 1191 1192 /** 1193 * @param curWidth the curWidth to set 1194 */ 1195 public void setCurWidth(double curWidth) { 1196 this.curWidth = curWidth; 1197 } 1198 1199 public PolarOrbitTrackDataSource getDataSource() { 1200 DataSourceImpl ds = null; 1201 List dataSources = getDataSources(); 1202 boolean gotit = false; 1203 if (! dataSources.isEmpty()) { 1204 int nsrc = dataSources.size(); 1205 for (int i = 0; i < nsrc; i++) { 1206 ds = (DataSourceImpl) dataSources.get(nsrc - i - 1); 1207 if (ds instanceof PolarOrbitTrackDataSource) { 1208 gotit = true; 1209 break; 1210 } 1211 } 1212 } 1213 if (! gotit) return null; 1214 return (PolarOrbitTrackDataSource) ds; 1215 } 1216 1217 /* (non-Javadoc) 1218 * @see ucar.unidata.idv.control.DisplayControlImpl#getDisplayListData() 1219 */ 1220 @Override 1221 protected Data getDisplayListData() { 1222 1223 // get time range that was specified in the Field Selector 1224 String startTime = (String) getDataInstance().getDataSelection().getProperties().get(TimeRangeSelection.PROP_BEGTIME); 1225 String endTime = (String) getDataInstance().getDataSelection().getProperties().get(TimeRangeSelection.PROP_ENDTIME); 1226 1227 // get the template used for the Display Properties Layer Label 1228 String labelTemplate = getDisplayListTemplate(); 1229 1230 // see if time macro is enabled 1231 boolean hasTimeMacro = UtcDate.containsTimeMacro(labelTemplate); 1232 1233 // fetch the label superclass would normally generate 1234 Data data = super.getDisplayListData(); 1235 1236 // if so, modify label with time range for this selection 1237 if (hasTimeMacro) { 1238 try { 1239 TextType tt = TextType.getTextType(DISPLAY_LIST_NAME); 1240 data = new Text(tt, data.toString() + startTime + " - " + endTime); 1241 } catch (VisADException vade) { 1242 logger.error("Problem creating text", vade); 1243 } 1244 } 1245 1246 // return either original or modified data object 1247 return data; 1248 } 1249 1250 public double getLatitude() { 1251 return latitude; 1252 } 1253 1254 public double getLongitude() { 1255 return longitude; 1256 } 1257 1258 /** 1259 * @return the otCurFont 1260 */ 1261 public Font getOtCurFont() { 1262 return otCurFont; 1263 } 1264 1265 /** 1266 * @param otCurFont the otCurFont to set 1267 */ 1268 public void setOtCurFont(Font otCurFont) { 1269 this.otCurFont = otCurFont; 1270 } 1271 1272 public String getStation() { 1273 return station; 1274 } 1275 1276 /** 1277 * @return the swathEdgesOn 1278 */ 1279 public boolean isSwathEdgesOn() { 1280 return swathEdgesOn; 1281 } 1282 1283 /** 1284 * @param swathEdgesOn the swathEdgesOn to set 1285 */ 1286 public void setSwathEdgesOn(boolean swathEdgesOn) { 1287 this.swathEdgesOn = swathEdgesOn; 1288 } 1289 1290 private float[][][] getSwath(float[][] track) { 1291 double earthRadius = AstroConst.R_Earth_mean / 1000.0; 1292 int npt = track[0].length - 1; 1293 float[][][] ret = new float[2][2][npt - 1]; 1294 try { 1295 int indx = 0; 1296 for (int i = 1; i < npt; i++) { 1297 double latA = Math.toRadians(track[0][i - 1]); 1298 double lonA = Math.toRadians(track[1][i - 1]); 1299 1300 double latB = Math.toRadians(track[0][i]); 1301 double lonB = Math.toRadians(track[1][i]); 1302 1303 double diffLon = lonB - lonA; 1304 double bX = cos(latB) * cos(diffLon); 1305 double bY = cos(latB) * sin(diffLon); 1306 double xFac = cos(latA) + bX; 1307 double latC = atan2(sin(latA) + sin(latB), sqrt(xFac * xFac + bY * bY)); 1308 double lonC = lonA + atan2(bY, xFac); 1309 1310 double bearing = atan2(sin(diffLon) * cos(latB), 1311 cos(latA) * sin(latB) - sin(latA) * cos(latB) * cos(diffLon)) 1312 + (Math.PI / 2.0); 1313 double dist = curWidth / 2.0; 1314 dist /= earthRadius; 1315 double lat = asin((sin(latC) * cos(dist)) + 1316 (cos(latC) * sin(dist) * cos(bearing))); 1317 double lon = lonC + atan2(sin(bearing) * sin(dist) * cos(latC), 1318 cos(dist) - (sin(latC) * sin(lat))); 1319 float latD = (float) Math.toDegrees(lat); 1320 float lonD = (float) Math.toDegrees(lon); 1321 1322 bearing += Math.PI; 1323 lat = asin((sin(latC) * cos(dist)) + 1324 (cos(latC) * sin(dist) * cos(bearing))); 1325 lon = lonC + atan2(sin(bearing) * sin(dist) * cos(latC), 1326 cos(dist) - (sin(latC) * sin(lat))); 1327 float latE = (float) Math.toDegrees(lat); 1328 float lonE = (float) Math.toDegrees(lon); 1329 1330 ret[0][0][indx] = latD; 1331 ret[0][1][indx] = lonD; 1332 1333 ret[1][0][indx] = latE; 1334 ret[1][1][indx] = lonE; 1335 ++indx; 1336 } 1337 } catch (Exception e) { 1338 logger.error("Problem getting swath", e); 1339 return null; 1340 } 1341 return ret; 1342 } 1343 1344 /** 1345 * Overridden by McIDAS-V so that we can force the {@code display name} to 1346 * {@literal "Satellite Orbit Track"} when loading from a bundle. 1347 * 1348 * <p>This is done because {@link #init(DataChoice)} will call 1349 * {@link #setDisplayName(String)} essentially like this: 1350 * {@code setDisplayName(getLongParamName() + " " + getDisplayName()}. 1351 * This results in the display name for a bundled orbit track control 1352 * being something like 1353 * {@literal "SUOMI NPP SUOMI NPP Satellite Orbit Track"}.</p> 1354 * 1355 * @param vc Context in which this control exists. 1356 * @param properties Properties that may hold things. 1357 * @param preSelectedDataChoices Set of preselected data choices. 1358 */ 1359 1360 @Override public void initAfterUnPersistence(ControlContext vc, 1361 Hashtable properties, 1362 List preSelectedDataChoices) 1363 { 1364 setDisplayName("Satellite Orbit Track"); 1365 super.initAfterUnPersistence(vc, properties, preSelectedDataChoices); 1366 jcbTrackLineStyle.setSelectedIndex(curTrackLineStyle); 1367 jcbEdgeLineStyle.setSelectedIndex(curEdgeLineStyle); 1368 jcbSwathCenterLineWidth.setSelectedIndex(curSwathCenterWidth - 1); 1369 jcbSwathEdgeLineWidth.setSelectedIndex(curSwathEdgeWidth - 1); 1370 1371 // no idea if these invokeLater calls should be grouped into a single 1372 // call or not :( 1373 1374 SwingUtilities.invokeLater(() -> { 1375 DefaultComboBoxModel<GroundStation> cbm = 1376 new DefaultComboBoxModel<>(); 1377 1378 for (GroundStation s : stations) { 1379 logger.trace("adding ground station {}", s); 1380 cbm.addElement(s); 1381 CurveDrawer cd = makeCoverageCircle(s); 1382 TextDisplayable td = labelGroundStation(s); 1383 stationToCurve.put(s, cd); 1384 stationToText.put(s, td); 1385 addDisplayable(cd); 1386 addDisplayable(td); 1387 // We don't know how many to expect, so update "current station" labels 1388 // with last one processed when restoring bundles. 1389 latLabel.setText(s.getElt().getLatitude().toString()); 1390 lonLabel.setText(s.getElt().getLongitude().toString()); 1391 altLabel.setText(s.getElt().getAltitude().toString()); 1392 } 1393 jcbStationsPlotted.setModel(cbm); 1394 }); 1395 1396 SwingUtilities.invokeLater(() -> { 1397 js.getModel().setValue(curLabelInterval); 1398 jcbLabels.getModel().setSelected(showingLabels); 1399 }); 1400 } 1401 1402 @Override public boolean init(DataChoice dataChoice) 1403 throws VisADException, RemoteException 1404 { 1405 logger.debug("init() in..."); 1406 1407 PolarOrbitTrackDataSource potdc = getDataSource(); 1408 1409 // Show tool tips immediately 1410 ToolTipManager.sharedInstance().setInitialDelay(0); 1411 1412 // if we're not coming back from a bundle, then we need to handle 1413 // otCurFont being null (was previously done in constructor) 1414 if (otCurFont == null) { 1415 otCurFont = FontSelector.DEFAULT_FONT; 1416 } 1417 otCurFontSize = otCurFont.getSize(); 1418 1419 if (potdc.getTrs() != null) { 1420 // validate time range before going ahead with control initialization 1421 if (! potdc.getTrs().begTimeOk()) { 1422 JOptionPane.showMessageDialog(null, 1423 "Invalid start time, must follow format HH:MM:SS", 1424 ERR_DIALOG_TITLE, JOptionPane.ERROR_MESSAGE); 1425 return false; 1426 } 1427 1428 if (! potdc.getTrs().endTimeOk()) { 1429 JOptionPane.showMessageDialog(null, 1430 "Invalid end time, must follow format HH:MM:SS", 1431 ERR_DIALOG_TITLE, JOptionPane.ERROR_MESSAGE); 1432 return false; 1433 } 1434 1435 if (! potdc.getTrs().timeRangeOk()) { 1436 JOptionPane.showMessageDialog(null, 1437 "Invalid time range selection, please correct", 1438 ERR_DIALOG_TITLE, JOptionPane.ERROR_MESSAGE); 1439 return false; 1440 } 1441 1442 // allow at most two full days of orbit tracks - more than this will 1443 // at best clutter the display and at worst grind McV indefinitely 1444 long timeDiff = potdc.getTrs().getTimeRangeInSeconds(); 1445 if (timeDiff >= (60 * 60 * 24 * 2)) { 1446 JOptionPane.showMessageDialog(null, 1447 "Time range greater than two full days is not allowed, please correct", 1448 ERR_DIALOG_TITLE, JOptionPane.ERROR_MESSAGE); 1449 return false; 1450 } 1451 } 1452 1453 // instantiate components we need to exist at initialization 1454 latLabel = new JLabel(); 1455 lonLabel = new JLabel(); 1456 altLabel = new JLabel(); 1457 1458 // create time label checkbox toggle, start out enabled 1459 jcbLabels = new JCheckBox("Labels On/Off"); 1460 jcbLabels.setSelected(true); 1461 jcbLabels.setName(CHECKBOX_LABELS); 1462 jcbLabels.addItemListener(this); 1463 1464 // create swath edges toggle, start out disabled 1465 jcbSwathEdges = new JCheckBox("Swath Edges On/Off"); 1466 jcbSwathEdges.setSelected(swathEdgesOn); 1467 jcbSwathEdges.setName(CHECKBOX_SWATH_EDGES); 1468 jcbSwathEdges.addItemListener(this); 1469 1470 // initialize the various swath and groundstation params 1471 jcbSwathCenterLineWidth.setSelectedIndex(curSwathCenterWidth - 1); 1472 jcbSwathEdgeLineWidth.setSelectedIndex(curSwathEdgeWidth - 1); 1473 jcbEdgeLineStyle.setSelectedIndex(curEdgeLineStyle); 1474 jcbTrackLineStyle.setSelectedIndex(curTrackLineStyle); 1475 1476 otFontSelector = new FontSelector(FontSelector.COMBOBOX_UI, false, false); 1477 otFontSelector.setFont(otCurFont); 1478 gsFontSelector = new FontSelector(FontSelector.COMBOBOX_UI, false, false); 1479 gsFontSelector.setFont(FontSelector.DEFAULT_FONT); 1480 1481 // Bump default font size down just a bit... 1482 gsFontSelector.setFontSize(9); 1483 otCurFont = otFontSelector.getFont(); 1484 otCurFontSize = otCurFont.getSize(); 1485 1486 this.dataChoice = dataChoice; 1487 String choiceName = dataChoice.getName(); 1488 NodeList nodeList = root.getElementsByTagName(TAG_SATELLITE); 1489 int num = nodeList.getLength(); 1490 if (num > 0) { 1491 for (int i = 0; i < num; i++) { 1492 Element n = (Element) (nodeList.item(i)); 1493 String satName = n.getAttribute(ATTR_NAME); 1494 if (satName.equals(choiceName)) { 1495 String strWidth = n.getAttribute(ATTR_WIDTH); 1496 if (strWidth.isEmpty()) strWidth = "0"; 1497 Double dWidth = Double.valueOf(strWidth); 1498 curWidth = dWidth.doubleValue(); 1499 break; 1500 } 1501 } 1502 } 1503 try { 1504 trackDsp = new CompositeDisplayable(); 1505 timeLabelDsp = new CompositeDisplayable(); 1506 swathEdgeDsp = new CompositeDisplayable(); 1507 } catch (Exception e) { 1508 logger.error("Problem creating composite displayable", e); 1509 return false; 1510 } 1511 boolean result = super.init((DataChoice) this.getDataChoices().get(0)); 1512 1513 String dispName = getDisplayName(); 1514 setDisplayName(getLongParamName() + ' ' + dispName); 1515 logger.debug("Setting display name: {}", getDisplayName()); 1516 try { 1517 String longName = 1518 REGEX.matcher(getLongParamName()).replaceAll(""); 1519 otTextType = new TextType(SWATH_MODS + longName); 1520 } catch (Exception e) { 1521 logger.trace("Problem creating texttype", e); 1522 otTextType = TextType.Generic; 1523 } 1524 1525 Data data = getData(getDataInstance()); 1526 createTrackDisplay(data, true); 1527 dataSource = getDataSource(); 1528 try { 1529 navDsp = getNavigatedDisplay(); 1530 float defaultZ = getMapViewManager().getDefaultMapPosition(); 1531 // we're just nudging a bit so tracks (and their labels) get drawn below 1532 // ground stations (and their labels), which get drawn over default map level 1533 // user can change this in map controls if they prefer maps on top 1534 gsZ = defaultZ + 0.02f; 1535 trackZ = defaultZ + 0.01f; 1536 // range on "map level" stuff is -1 to 1, stay within these limits 1537 if (trackZ > 1.0f) trackZ = 0.9f; 1538 if (gsZ > 1.0f) gsZ = 1.0f; 1539 if (! inGlobeDisplay()) { 1540 applyDisplayableLevels(); 1541 } 1542 } catch (Exception e) { 1543 logger.error("Problem getting display center", e); 1544 } 1545 1546 // set the default legend label template 1547 setLegendLabelTemplate(DisplayControlBase.MACRO_DISPLAYNAME); 1548 1549 return result; 1550 } 1551 1552 @Override 1553 public void itemStateChanged(ItemEvent ie) { 1554 1555 // now we have multiple checkboxes, so first see which one applies 1556 String source = ((JCheckBox) ie.getSource()).getName(); 1557 try { 1558 if (source.equals(CHECKBOX_LABELS)) { 1559 if (ie.getStateChange() == ItemEvent.DESELECTED) { 1560 timeLabelDsp.setVisible(false); 1561 } else { 1562 if (timeLabelDsp.displayableCount() > 0) { 1563 // TJJ Apr 2019 - see if scale changed, if so need to redraw 1564 float currentScale = getViewManager().getMaster().getDisplayScale(); 1565 if (Float.compare(currentScale, scale) != 0) { 1566 scale = currentScale; 1567 redrawAll(); 1568 } else { 1569 timeLabelDsp.setVisible(true); 1570 } 1571 } else { 1572 redrawAll(); 1573 } 1574 } 1575 } 1576 if (source.equals(CHECKBOX_SWATH_EDGES)) { 1577 // There must first be a valid swath width before we can draw edges 1578 // Test the current value in the text input and update if appropriate 1579 if (ie.getStateChange() == ItemEvent.DESELECTED) { 1580 swathEdgeDsp.setVisible(false); 1581 swathEdgesOn = false; 1582 } else { 1583 swathEdgesOn = true; 1584 float newSwathWidth = validateSwathWidthField(); 1585 if (newSwathWidth > 0) { 1586 curWidth = newSwathWidth; 1587 if (Double.compare(curWidth, prvWidth) != 0) { 1588 prvWidth = curWidth; 1589 redrawAll(); 1590 } else { 1591 if (swathEdgeDsp.displayableCount() > 0) { 1592 swathEdgeDsp.setVisible(true); 1593 } else { 1594 redrawAll(); 1595 } 1596 } 1597 } 1598 } 1599 } 1600 } catch (VisADException | RemoteException e) { 1601 logger.error("Problem handing state change", e); 1602 } 1603 1604 } 1605 1606 private TextDisplayable labelGroundStation(GroundStation station) { 1607 TextDisplayable groundStationDsp = null; 1608 try { 1609 String str = "+ " + station; 1610 logger.debug("Drawing station: {}", str); 1611 TextType tt = new TextType(STATION_MODS + ttCounter); 1612 groundStationDsp = 1613 new TextDisplayable(STATION_MODS + jcbStationsPlotted.getItemCount(), tt); 1614 ttCounter++; 1615 groundStationDsp.setJustification(TextControl.Justification.LEFT); 1616 groundStationDsp.setVerticalJustification(TextControl.Justification.CENTER); 1617 groundStationDsp.setColor(station.getColor()); 1618 groundStationDsp.setFont(station.getFont()); 1619 groundStationDsp.setTextSize((float) scale * station.getFont().getSize() / FONT_SCALE_FACTOR); 1620 groundStationDsp.setSphere(inGlobeDisplay()); 1621 DisplayRealType dispType = navDsp.getDisplayAltitudeType(); 1622 groundStationDsp.setConstantPosition(gsZ, dispType); 1623 1624 double dlat = station.getElt().getValues()[0]; 1625 double dlon = station.getElt().getValues()[1]; 1626 RealTuple lonLat = 1627 new RealTuple(RealTupleType.SpatialEarth2DTuple, 1628 new double[] { dlon, dlat }); 1629 Tuple tup = new Tuple(makeTupleType(tt), 1630 new Data[] { lonLat, new Text(tt, str)}); 1631 groundStationDsp.setData(tup); 1632 stationToText.put(station, groundStationDsp); 1633 } catch (Exception e) { 1634 logger.error("Problem drawing station", e); 1635 } 1636 return groundStationDsp; 1637 } 1638 1639 private JPanel makeGroundStationPanel() { 1640 JPanel jp = new JPanel(new MigLayout()); 1641 jp.setBorder(BorderFactory.createTitledBorder(" Ground Station Controls ")); 1642 1643 jcbStationLineStyle = new JComboBox<>(Constants.lineStyles); 1644 jcbStationLineStyle.addActionListener(this); 1645 jcbStationLineStyle.setSelectedIndex(1); 1646 1647 locationComboBox = new JComboBox<>(); 1648 jcbStationsPlotted = new JComboBox<>(); 1649 jcbStationsPlotted.addItemListener(event -> { 1650 logger.debug("Active Station changed..."); 1651 GroundStation gs = (GroundStation) jcbStationsPlotted.getSelectedItem(); 1652 if (gs != null) { 1653 // Update UI with settings for this station 1654 updateGroundStationWidgets(gs); 1655 } 1656 }); 1657 1658 // Ground Stations are now a natural-order map (alphabetical) 1659 GroundStations gs = new GroundStations(null); 1660 GuiUtils.setListData(locationComboBox, gs.getGroundStations()); 1661 1662 // initialize reasonable output for no stations plotted yet 1663 if (locationComboBox.getItemCount() > 0) { 1664 latLabel.setText(" - "); 1665 lonLabel.setText(" - "); 1666 altLabel.setText(" - "); 1667 } 1668 1669 locationPanel = new JPanel(); 1670 locationPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 1671 locationPanel.add(new JLabel("Ground Stations Available:")); 1672 locationPanel.add(locationComboBox); 1673 JButton addButton = new JButton("Add Selected"); 1674 addButton.setToolTipText(TOOLTIP_ADD_SELECTED); 1675 addButton.setActionCommand(STATION_ADD); 1676 addButton.addActionListener(this); 1677 locationPanel.add(addButton); 1678 1679 JPanel customPanel = new JPanel(); 1680 customPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 1681 customPanel.add(new JLabel("Custom Ground Station: Label:")); 1682 customLab = new JTextField(6); 1683 customLab.setToolTipText(TOOLTIP_CUSTOM_LABEL); 1684 customPanel.add(customLab); 1685 customPanel.add(new JLabel("Lat:")); 1686 customLat = new JTextField(6); 1687 customLat.setToolTipText(TOOLTIP_CUSTOM_LAT); 1688 customPanel.add(customLat); 1689 customPanel.add(new JLabel("Lon:")); 1690 customLon = new JTextField(6); 1691 customLon.setToolTipText(TOOLTIP_CUSTOM_LON); 1692 customPanel.add(customLon); 1693 customPanel.add(new JLabel("Alt:")); 1694 customAlt = new JTextField(6); 1695 customAlt.setToolTipText(TOOLTIP_CUSTOM_ALT); 1696 customPanel.add(customAlt); 1697 customPanel.add(new JLabel("m")); 1698 JButton customButton = new JButton("Add Custom"); 1699 customButton.setToolTipText(TOOLTIP_ADD_CUSTOM); 1700 customButton.setActionCommand(CUSTOM_ADD); 1701 customButton.addActionListener(this); 1702 customPanel.add(customButton); 1703 1704 JPanel plottedStationsPanel = new JPanel(); 1705 plottedStationsPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 1706 plottedStationsPanel.add(new JLabel("Ground Stations Plotted:")); 1707 plottedStationsPanel.add(jcbStationsPlotted); 1708 JButton remButton = new JButton("Remove Selected"); 1709 remButton.setActionCommand(STATION_REM); 1710 remButton.addActionListener(this); 1711 plottedStationsPanel.add(remButton); 1712 1713 latLonAltPanel = new JPanel(); 1714 latLonAltPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 1715 1716 latLonAltPanel.add(new JLabel("Current Station, Latitude: ")); 1717 latLonAltPanel.add(latLabel); 1718 latLonAltPanel.add(Box.createHorizontalStrut(5)); 1719 1720 latLonAltPanel.add(new JLabel("Longitude: ")); 1721 latLonAltPanel.add(lonLabel); 1722 latLonAltPanel.add(Box.createHorizontalStrut(5)); 1723 1724 latLonAltPanel.add(new JLabel("Altitude: ")); 1725 latLonAltPanel.add(altLabel); 1726 1727 JPanel gsFontPanel = new JPanel(new FlowLayout(FlowLayout.LEFT)); 1728 gsFontPanel.add(new JLabel("Font: ")); 1729 gsFontPanel.add(gsFontSelector.getComponent()); 1730 1731 Color swatchAntColor = getAntColor(); 1732 antColorSwatch = new ColorSwatchComponent(getStore(), swatchAntColor, "Color"); 1733 antColorSwatch.setPreferredSize(Constants.DEFAULT_COLOR_PICKER_SIZE); 1734 1735 antColorPanel = new JPanel(); 1736 antColorPanel.setLayout(new FlowLayout(FlowLayout.LEFT)); 1737 antColorPanel.add(new JLabel("Color: ")); 1738 antColorPanel.add(antColorSwatch); 1739 1740 antColorPanel.add(Box.createHorizontalStrut(5)); 1741 antColorPanel.add(new JLabel("Line Width: ")); 1742 antColorPanel.add(jcbStationLineWidth); 1743 1744 antColorPanel.add(Box.createHorizontalStrut(5)); 1745 antColorPanel.add(new JLabel("Line Style: ")); 1746 antColorPanel.add(jcbStationLineStyle); 1747 1748 antColorPanel.add(Box.createHorizontalStrut(5)); 1749 antColorPanel.add(new JLabel("Antenna Angle: ")); 1750 antennaAngle.setToolTipText(TOOLTIP_ANTENNA_ANGLE); 1751 antColorPanel.add(antennaAngle); 1752 1753 jp.add(locationPanel, "wrap"); 1754 jp.add(customPanel, "wrap"); 1755 jp.add(plottedStationsPanel, "wrap"); 1756 jp.add(latLonAltPanel, "wrap"); 1757 jp.add(Box.createVerticalStrut(5), "wrap"); 1758 jp.add(gsFontPanel, "wrap"); 1759 jp.add(antColorPanel, "wrap"); 1760 jp.add(makeBottomRow(STATION_MODS)); 1761 return jp; 1762 } 1763 1764 /** 1765 * Create the {@literal "Apply"} button used by both sections of the 1766 * control's GUI. 1767 * 1768 * @param command {@literal "Command"} used in 1769 * {@link #actionPerformed(ActionEvent)}. 1770 * 1771 * @return {@code JPanel} containing our {@literal "Apply"} button, 1772 * suitable for adding to the end of the control's bordered panels. 1773 */ 1774 1775 private JPanel makeBottomRow(String command) { 1776 JPanel row = new JPanel(new FlowLayout(FlowLayout.LEFT)); 1777 JButton applyButton = new JButton("Apply"); 1778 applyButton.setActionCommand(command); 1779 applyButton.addActionListener(this); 1780 row.add(applyButton); 1781 return row; 1782 } 1783 1784 private JPanel makeSwathWidthPanel() { 1785 if (dataChoice != null) 1786 satelliteName = new JLabel(dataChoice.getName()); 1787 swathWidthFld = new JTextField("" + curWidth, 5); 1788 if (curWidth == 0) swathWidthFld.setText(SWATH_NA); 1789 1790 JPanel jp = new JPanel(new FlowLayout(FlowLayout.LEFT)); 1791 1792 // first on this panel, check box to turn on/off swath line edges 1793 jp.add(jcbSwathEdges); 1794 1795 jp.add(Box.createHorizontalStrut(5)); 1796 jp.add(new JLabel("Satellite: ")); 1797 jp.add(satelliteName); 1798 jp.add(Box.createHorizontalStrut(5)); 1799 jp.add(new JLabel("Swath Width: ")); 1800 swathWidthFld.setToolTipText(TOOLTIP_SWATH_WIDTH); 1801 jp.add(swathWidthFld); 1802 jp.add(kmLabel); 1803 jp.add(Box.createHorizontalStrut(5)); 1804 1805 return jp; 1806 } 1807 1808 private TupleType makeTupleType(TextType tt) { 1809 TupleType t = null; 1810 try { 1811 t = new TupleType(new MathType[] {RealTupleType.SpatialEarth2DTuple, tt}); 1812 } catch (Exception e) { 1813 logger.error("Problem creating TupleType", e); 1814 } 1815 return t; 1816 } 1817 1818 private void addGroundStation(GroundStation gs, boolean isCustom) { 1819 1820 logger.debug("addGroundStation() in, name: {}", gs.getName()); 1821 1822 try { 1823 1824 EarthLocationTuple elt = gs.getElt(); 1825 latLabel.setText(elt.getLatitude().toString()); 1826 lonLabel.setText(elt.getLongitude().toString()); 1827 altLabel.setText(elt.getAltitude().toString()); 1828 1829 // quick and easy way to limit sig digits to something not too crazy 1830 if (altLabel.getText().length() > 10) altLabel.setText(altLabel.getText().substring(0, 9)); 1831 latitude = Double.parseDouble(latLabel.getText()); 1832 longitude = Double.parseDouble(lonLabel.getText()); 1833 1834 // For non-custom, "stock" groundstations, compute altitude 1835 // For custom, the user will have specified it 1836 if (! isCustom) { 1837 double altitude = dataSource.getNearestAltToGroundStation(latitude, longitude) / 1000.0; 1838 gs.setAltitude(altitude); 1839 } 1840 gs.setColor(antColorSwatch.getColor()); 1841 gs.setLineWidth(jcbStationLineWidth.getSelectedIndex() + 1); 1842 gs.setLineStyle(jcbStationLineStyle.getSelectedIndex()); 1843 gs.setFont(gsFontSelector.getFont()); 1844 CurveDrawer cd = makeCoverageCircle(gs); 1845 1846 if (cd != null) { 1847 logger.debug("Adding ground station, station name: {}", gs.getName()); 1848 TextDisplayable label = labelGroundStation(gs); 1849 cd.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType()); 1850 addDisplayable(cd); 1851 stationToCurve.put(gs, cd); 1852 stationToText.put(gs, label); 1853 } else { 1854 logger.error("could not draw curve!!"); 1855 } 1856 1857 TextDisplayable td = stationToText.get(gs); 1858 td.setConstantPosition(gsZ, navDsp.getDisplayAltitudeType()); 1859 addDisplayable(td); 1860 } catch (Exception e) { 1861 logger.error("Problem adding ground station", e); 1862 } 1863 } 1864 1865 private void updateStationColor(GroundStation gs, Color newColor) 1866 throws VisADException, RemoteException 1867 { 1868 gs.setColor(newColor); 1869 stationToCurve.get(gs).setColor(newColor); 1870 stationToText.get(gs).setColor(newColor); 1871 } 1872 1873 private void updateStationFont(GroundStation gs, Font newFont) 1874 throws VisADException, RemoteException 1875 { 1876 gs.setFont(newFont); 1877 TextDisplayable label = stationToText.get(gs); 1878 label.setFont(newFont); 1879 scale = getViewManager().getMaster().getDisplayScale(); 1880 label.setTextSize((float) scale * newFont.getSize() / FONT_SCALE_FACTOR); 1881 } 1882 1883 private void replaceCurve(GroundStation gs) 1884 throws VisADException, RemoteException 1885 { 1886 gs.setLineWidth(jcbStationLineWidth.getSelectedIndex() + 1); 1887 gs.setLineStyle(jcbStationLineStyle.getSelectedIndex()); 1888 CurveDrawer cdOld = stationToCurve.get(gs); 1889 CurveDrawer cdNew = makeCoverageCircle(gs); 1890 TextDisplayable label = stationToText.get(gs); 1891 cdNew.setLineWidth(gs.getLineWidth()); 1892 removeDisplayable(cdOld); 1893 removeDisplayable(label); 1894 addDisplayable(cdNew); 1895 addDisplayable(label); 1896 stationToCurve.put(gs, cdNew); 1897 } 1898 1899 private void updateGroundStationWidgets(GroundStation gs) { 1900 gsFontSelector.setFont(gs.getFont()); 1901 antColorSwatch.setBackground(gs.getColor()); 1902 jcbStationLineStyle.setSelectedIndex(gs.getLineStyle()); 1903 jcbStationLineWidth.setSelectedIndex(gs.getLineWidth() - 1); 1904 antennaAngle.setText(String.valueOf(gs.getAntennaAngle())); 1905 curAngle = gs.getAntennaAngle(); 1906 curElevation = (int) gs.getAltitude(); 1907 1908 EarthLocation elt = gs.getElt(); 1909 latLabel.setText(String.valueOf(elt.getLatitude().getValue())); 1910 lonLabel.setText(String.valueOf(elt.getLongitude().getValue())); 1911 altLabel.setText(String.valueOf(elt.getAltitude().getValue())); 1912 } 1913 1914 public void setStations(List<GroundStation> newStations) { 1915 stations.clear(); 1916 stations.addAll(newStations); 1917 } 1918 1919 public List<GroundStation> getStations() { 1920 // this clear() call is important! 1921 // if not done, saving more than one bundle will result in the 1922 // "stations" list containing the contents of "jcbStationsPlotted" 1923 // for *every call to getStations()!* 1924 stations.clear(); 1925 for (int i = 0; i < jcbStationsPlotted.getItemCount(); i++) { 1926 stations.add(jcbStationsPlotted.getItemAt(i)); 1927 } 1928 return stations; 1929 } 1930 1931 public void setShowingLabels(boolean newValue) { 1932 showingLabels = newValue; 1933 } 1934 1935 public boolean getShowingLabels() { 1936 return jcbLabels.isSelected(); 1937 } 1938 1939 /* (non-Javadoc) 1940 * @see ucar.unidata.idv.control.DisplayControlImpl#projectionChanged() 1941 */ 1942 @Override 1943 public void projectionChanged() { 1944 super.projectionChanged(); 1945 applyDisplayableLevels(); 1946 } 1947 1948 public void setAntColor(Color c) { 1949 if (c == null) c = defaultAntColor; 1950 try { 1951 antColor = c; 1952 } catch (Exception e) { 1953 logger.error("Exception in PolarOrbitTrackControl.setAntColor", e); 1954 } 1955 } 1956 1957 public void setStation(String val) { 1958 station = val.trim(); 1959 } 1960 1961 private float validateSwathWidthField() { 1962 String s = swathWidthFld.getText().trim(); 1963 float val = -1.0f; 1964 try { 1965 val = Float.parseFloat(s); 1966 } catch (NumberFormatException nfe) { 1967 // TJJ Jun 2015 - if GEO sensor, N/A means return invalid, but no warning msg needed 1968 if ((s != null) && (s.equals(SWATH_NA))) { 1969 return -2; 1970 } 1971 // throw up a dialog to tell user the problem 1972 JOptionPane.showMessageDialog(latLonAltPanel, 1973 "Invalid swath width: must be a decimal value in km"); 1974 return -1; 1975 } 1976 1977 // Need <= on low end because value must be positive 1978 if ((val <= SWATH_WIDTH_MIN) || (val > SWATH_WIDTH_MAX)) { 1979 // throw up a dialog to tell user the problem 1980 JOptionPane.showMessageDialog(latLonAltPanel, 1981 "Swath width valid range is > " + SWATH_WIDTH_MIN + 1982 " to " + SWATH_WIDTH_MAX + " km"); 1983 return -1; 1984 } 1985 return val; 1986 } 1987 1988 /* (non-Javadoc) 1989 * @see ucar.unidata.idv.control.DisplayControlImpl#showProperties() 1990 * We need this because the TimeSelection widget in preview window 1991 * and properties window are two different objects we are trying to 1992 * keep in sync, and only redraw the display when necessary. 1993 */ 1994 1995 @Override public void showProperties() { 1996 oldProps = new Hashtable(getDataInstance().getDataSelection().getProperties()); 1997 1998 JTabbedPane jtp = new JTabbedPane(); 1999 addPropertiesComponents(jtp); 2000 final JDialog propertiesDialog = GuiUtils.createDialog("Properties -- " + getTitle(), true); 2001 ActionListener listener = event -> { 2002 String cmd = event.getActionCommand(); 2003 if (cmd.equals(GuiUtils.CMD_OK) || cmd.equals(GuiUtils.CMD_APPLY)) { 2004 if (! applyProperties()) { 2005 return; 2006 } 2007 PolarOrbitTrackDataSource ds = getDataSource(); 2008 ds.setSelectionProps(getDataSelection().getProperties()); 2009 if (! oldProps.equals(getDataSelection().getProperties())) { 2010 redrawAll(); 2011 oldProps = getDataSelection().getProperties(); 2012 } 2013 } 2014 if (cmd.equals(GuiUtils.CMD_OK) || cmd.equals(GuiUtils.CMD_CANCEL)) { 2015 propertiesDialog.dispose(); 2016 } 2017 }; 2018 Window f = GuiUtils.getWindow(getContents()); 2019 JComponent buttons = GuiUtils.makeApplyOkCancelButtons(listener); 2020 JComponent propContents = inset(centerBottom(jtp, buttons), 5); 2021 Msg.translateTree(jtp, true); 2022 propertiesDialog.getContentPane().add(propContents); 2023 propertiesDialog.pack(); 2024 if (f != null) { 2025 GuiUtils.showDialogNearSrc(f, propertiesDialog); 2026 } else { 2027 propertiesDialog.setVisible(true); 2028 } 2029 } 2030}