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}