001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2023 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.chooser.adde; 030 031import static javax.swing.GroupLayout.DEFAULT_SIZE; 032import static javax.swing.GroupLayout.PREFERRED_SIZE; 033import static javax.swing.GroupLayout.Alignment.BASELINE; 034import static javax.swing.GroupLayout.Alignment.LEADING; 035import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 036 037import java.awt.FlowLayout; 038import java.awt.BorderLayout; 039import java.util.ArrayList; 040import java.util.Hashtable; 041import java.util.Iterator; 042import java.util.List; 043import java.util.StringTokenizer; 044 045import javax.swing.GroupLayout; 046import javax.swing.JComboBox; 047import javax.swing.JComponent; 048import javax.swing.JLabel; 049import javax.swing.JPanel; 050import javax.swing.JTextField; 051import javax.swing.JOptionPane; 052 053import org.w3c.dom.Element; 054 055import edu.wisc.ssec.mcidas.AreaDirectory; 056import edu.wisc.ssec.mcidas.AreaDirectoryList; 057import edu.wisc.ssec.mcidas.AreaFileException; 058import edu.wisc.ssec.mcidas.McIDASUtil; 059 060import ucar.unidata.data.imagery.AddeImageInfo; 061import ucar.unidata.data.imagery.ImageDataSource; 062import ucar.unidata.idv.chooser.IdvChooserManager; 063import ucar.unidata.idv.chooser.adde.AddeServer; 064import ucar.unidata.metdata.NamedStationTable; 065import ucar.unidata.util.GuiUtils; 066import ucar.unidata.util.LogUtil; 067import ucar.unidata.util.Misc; 068 069 070import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 071import ucar.unidata.xml.XmlObjectStore; 072 073/** 074 * Widget to select NEXRAD radar images from a remote ADDE server 075 * Displays a list of the descriptors (names) of the radar datasets 076 * available for a particular ADDE group on the remote server. 077 * 078 * @author Don Murray 079 */ 080public class AddeRadarChooser extends AddeImageChooser { 081 082 /** Use to list the stations */ 083 protected static final String VALUE_LIST = "list"; 084 085 /** This is the list of properties that are used in the advanced gui */ 086 private static final String[] RADAR_PROPS = { PROP_UNIT }; 087 088 /** This is the list of labels used for the advanced gui */ 089 private static final String[] RADAR_LABELS = { "Data Type:" }; 090 091 /** Am I currently reading the stations */ 092 private boolean readingStations = false; 093 094 /** handle on the station update task */ 095 private Object readStationTask; 096 097 /** station table */ 098 private List nexradStations; 099 100 private static final String DEFAULT_ARCHIVE_IMAGE_COUNT = "100"; 101 102 103 104 /** 105 * Construct an Adde image selection widget displaying information 106 * for the specified dataset located on the specified server. 107 * 108 * 109 * 110 * @param mgr The chooser manager 111 * @param root The chooser.xml node 112 */ 113 public AddeRadarChooser(IdvChooserManager mgr, Element root) { 114 super(mgr, root); 115 this.nexradStations = 116 getIdv().getResourceManager().findLocationsByType("radar"); 117 String numImage = getIdv().getStore().get(PREF_NUM_IMAGE_PRESET_RADARCHOOSER, AddeRadarChooser.DEFAULT_ARCHIVE_IMAGE_COUNT); 118 imageCountTextField = new JTextField(numImage, 4); 119 imageCountTextField.addActionListener(e -> readTimes(false)); 120 imageCountTextField.setToolTipText( 121 "<html>Enter a numerical value or the word ALL and press Enter<br/><br/>" + 122 "By default, up to the 100 most recent times are listed.<br/><br/>" + 123 "You may set this field to any positive integer, or the value ALL.<br/>" + 124 "Using ALL may take awhile for datasets with many times.</html>" 125 ); 126 } 127 128 /** 129 * get the adde server grup type to use 130 * 131 * @return group type 132 */ 133 protected String getGroupType() { 134 return AddeServer.TYPE_RADAR; 135 } 136 137 /** 138 * Overwrite base class method to return the correct name 139 * (used for labeling, etc.) 140 * 141 * @return data name specific to this selector 142 */ 143 public String getDataName() { 144 return "Radar Data"; 145 } 146 147 @Override public String getDataType() { 148 return "RADAR"; 149 } 150 151 /** 152 * _more_ 153 * 154 * @return _more_ 155 */ 156 public String getDescriptorLabel() { 157 return "Product"; 158 } 159 160 /** 161 * Get the size of the image list 162 * 163 * @return the image list size 164 */ 165 protected int getImageListSize() { 166 return 6; 167 } 168 169 /** 170 * Get a description of the currently selected dataset 171 * 172 * @return the data set description. 173 */ 174 public String getDatasetName() { 175 return getSelectedStation() + " (" + super.getDatasetName() + ")"; 176 } 177 178 /** 179 * Method to call if the server changed. 180 */ 181 protected void connectToServer() { 182 clearStations(); 183 super.connectToServer(); 184 setAvailableStations(); 185 } 186 187 /** 188 * Check if we are ready to read times 189 * 190 * @return true if times can be read 191 */ 192 protected boolean canReadTimes() { 193 return super.canReadTimes() && (getSelectedStation() != null); 194 } 195 196 /** 197 * Get the advanced property names 198 * 199 * @return array of advanced properties 200 */ 201 protected String[] getAdvancedProps() { 202 return RADAR_PROPS; 203 } 204 205 /** 206 * Get the labels for the advanced properties 207 * 208 * @return array of labels 209 */ 210 protected String[] getAdvancedLabels() { 211 return RADAR_LABELS; 212 } 213 214 /** 215 * Update labels, etc. 216 */ 217 protected void updateStatus() { 218 super.updateStatus(); 219 if (getState() != STATE_CONNECTED) { 220 clearStations(); 221 } 222 if (readStationTask!=null) { 223 if(taskOk(readStationTask)) { 224 setStatus("Reading available stations from server"); 225 } else { 226 readStationTask = null; 227 setState(STATE_UNCONNECTED); 228 } 229 } 230 } 231 232 /** 233 * A new station was selected. Update the gui. 234 * 235 * @param stations List of selected stations 236 */ 237 protected void newSelectedStations(List stations) { 238 super.newSelectedStations(stations); 239 descriptorChanged(); 240 } 241 242 /** 243 * Generate a list of radar ids for the id list. 244 */ 245 private void setAvailableStations() { 246 readStationTask = startTask(); 247 clearSelectedStations(); 248 updateStatus(); 249 List stations = readStations(); 250 if(stopTaskAndIsOk(readStationTask)) { 251 readStationTask = null; 252 if (stations != null) { 253 getStationMap().setStations(stations); 254 } else { 255 clearStations(); 256 } 257 updateStatus(); 258 revalidate(); 259 } else { 260 //User pressed cancel 261 setState(STATE_UNCONNECTED); 262 return; 263 } 264 } 265 266 /** 267 * Generate a list of radar ids for the id list. 268 * 269 * @return list of station IDs 270 */ 271 private List readStations() { 272 ArrayList stations = new ArrayList(); 273 try { 274 if ((descriptorNames == null) || (descriptorNames.length == 0)) { 275 return stations; 276 } 277 StringBuffer buff = getGroupUrl(REQ_IMAGEDIR, getGroup()); 278 String descrForIds = descriptorNames[0]; 279 // try to use base reflectivity if it's available. 280 for (int i = 0; i < descriptorNames.length; i++) { 281 if ((descriptorNames[i] != null) 282 && descriptorNames[i].toLowerCase().startsWith( 283 "base")) { 284 descrForIds = descriptorNames[i]; 285 break; 286 } 287 } 288 appendKeyValue(buff, PROP_DESCR, 289 getDescriptorFromSelection(descrForIds)); 290 appendKeyValue(buff, PROP_ID, VALUE_LIST); 291 Hashtable seen = new Hashtable(); 292 AreaDirectoryList dirList = 293 new AreaDirectoryList(buff.toString()); 294 for (Iterator it = dirList.getDirs().iterator(); it.hasNext(); ) { 295 AreaDirectory ad = (AreaDirectory) it.next(); 296 String stationId = 297 McIDASUtil.intBitsToString(ad.getValue(20)).trim(); 298 //Check for uniqueness 299 if (seen.get(stationId) != null) { 300 continue; 301 } 302 seen.put(stationId, stationId); 303 //System.err.println ("id:" + stationId); 304 Object station = findStation(stationId); 305 if (station != null) { 306 stations.add(station); 307 } 308 } 309 } catch (AreaFileException e) { 310 String msg = e.getMessage(); 311 if (msg.toLowerCase().indexOf( 312 "no images meet the selection criteria") >= 0) { 313 LogUtil.userErrorMessage( 314 "No stations could be found on the server"); 315 } else { 316 handleConnectionError(e); 317 } 318 stations = new ArrayList(); 319 setState(STATE_UNCONNECTED); 320 } 321 return stations; 322 } 323 324 /** 325 * Find the station for the given ID 326 * 327 * @param stationId the station ID 328 * 329 * @return the station or null if not found 330 */ 331 private Object findStation(String stationId) { 332 for (int i = 0; i < nexradStations.size(); i++) { 333 NamedStationTable table = 334 (NamedStationTable) nexradStations.get(i); 335 Object station = table.get(stationId); 336 if (station != null) { 337 return station; 338 } 339 } 340 return null; 341 } 342 343 public void doCancel() { 344 readStationTask = null; 345 super.doCancel(); 346 } 347 348 /** 349 * Get the list of properties for the base URL 350 * @return list of properties 351 */ 352 protected String[] getBaseUrlProps() { 353 return new String[] { PROP_DESCR, PROP_ID, PROP_UNIT, PROP_SPAC, 354 PROP_BAND, PROP_USER, PROP_PROJ, }; 355 } 356 357 /** 358 * Overwrite the base class method to return the default property value 359 * for PROP_ID. 360 * 361 * @param prop The property 362 * @param ad The area directory 363 * @param forDisplay Is this to show the end user in the gui. 364 * 365 * @return The value of the property 366 */ 367 protected String getDefaultPropValue(String prop, AreaDirectory ad, 368 boolean forDisplay) { 369 if (prop.equals(PROP_ID)) { 370 return getSelectedStation(); 371 } 372 if (prop.equals(PROP_SPAC)) { 373 // Don't want this to default to "1" or it will break 374 // Hydrometeor Classification product...see inquiry 1518 375 return "4"; 376 } 377 return super.getDefaultPropValue(prop, ad, forDisplay); 378 } 379 380 /** 381 * Get a description of the properties 382 * 383 * @return a description 384 */ 385 protected String getPropertiesDescription() { 386 StringBuilder buf = new StringBuilder(); 387 if (unitComboBox != null) { 388 buf.append(getAdvancedLabels()[0]); 389 buf.append(' '); 390 buf.append(unitComboBox.getSelectedItem()); 391 } 392 return buf.toString(); 393 } 394 395 /** 396 * get properties 397 * 398 * @param ht properties 399 */ 400 protected void getDataSourceProperties(Hashtable ht) { 401 unitComboBox.setSelectedItem(ALLUNITS); 402 super.getDataSourceProperties(ht); 403 ht.put(ImageDataSource.PROP_IMAGETYPE, ImageDataSource.TYPE_RADAR); 404 } 405 406 /** 407 * Get the time popup widget 408 * 409 * @return a widget for selecing the day 410 */ 411 protected JComponent getExtraTimeComponent() { 412 JPanel filler = new JPanel(); 413 McVGuiUtils.setComponentHeight(filler, new JComboBox()); 414 return filler; 415 } 416 417 /** 418 * Make the UI for this selector. 419 * 420 * @return The gui 421 */ 422 public JComponent doMakeContents() { 423 JPanel myPanel = new JPanel(); 424 425 JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:"); 426 addServerComp(stationLabel); 427 428 JComponent stationPanel = getStationMap(); 429 registerStatusComp("stations", stationPanel); 430 addServerComp(stationPanel); 431 432 JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:"); 433 addDescComp(timesLabel); 434 435 JPanel timesPanel = makeTimesPanel(); 436 timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); 437 addDescComp(timesPanel); 438 439 // We need to create this but never show it... AddeImageChooser requires it to be instantiated 440 unitComboBox = new JComboBox(); 441 442 enableWidgets(); 443 444 GroupLayout layout = new GroupLayout(myPanel); 445 myPanel.setLayout(layout); 446 layout.setHorizontalGroup( 447 layout.createParallelGroup(LEADING) 448 .addGroup(layout.createSequentialGroup() 449 .addGroup(layout.createParallelGroup(LEADING) 450 .addGroup(layout.createSequentialGroup() 451 .addComponent(descriptorLabel) 452 .addGap(GAP_RELATED) 453 .addComponent(descriptorComboBox)) 454 .addGroup(layout.createSequentialGroup() 455 .addComponent(stationLabel) 456 .addGap(GAP_RELATED) 457 .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 458 .addGroup(layout.createSequentialGroup() 459 .addComponent(timesLabel) 460 .addGap(GAP_RELATED) 461 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))) 462 ); 463 layout.setVerticalGroup( 464 layout.createParallelGroup(LEADING) 465 .addGroup(layout.createSequentialGroup() 466 .addGroup(layout.createParallelGroup(BASELINE) 467 .addComponent(descriptorLabel) 468 .addComponent(descriptorComboBox)) 469 .addPreferredGap(RELATED) 470 .addGroup(layout.createParallelGroup(LEADING) 471 .addComponent(stationLabel) 472 .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)) 473 .addPreferredGap(RELATED) 474 .addGroup(layout.createParallelGroup(LEADING) 475 .addComponent(timesLabel) 476 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))) 477 ); 478 479 setInnerPanel(myPanel); 480 return super.doMakeContents(true); 481 } 482 483 /** 484 * Get the default value for a key 485 * 486 * @return null for SIZE, else super 487 */ 488 protected String getDefault(String property, String dflt) { 489 if (PROP_SIZE.equals(property)) { 490 return dflt; 491 } 492 return super.getDefault(property, dflt); 493 } 494 495 /** 496 * Make an AddeImageInfo from a URL and an AreaDirectory 497 * 498 * @param dir 499 * AreaDirectory 500 * @param isRelative 501 * true if is relative 502 * @param num 503 * number (for relative images) 504 * 505 * @return corresponding AddeImageInfo 506 */ 507 protected AddeImageInfo makeImageInfo(AreaDirectory dir, 508 boolean isRelative, int num) { 509 AddeImageInfo info = new AddeImageInfo(getAddeServer().getName(), 510 AddeImageInfo.REQ_IMAGEDATA, getGroup(), getDescriptor()); 511 if (isRelative) { 512 info.setDatasetPosition((num == 0) ? 0 : -num); 513 } else { 514 info.setStartDate(dir.getNominalTime()); 515 } 516 setImageInfoProps(info, getMiscKeyProps(), dir); 517 setImageInfoProps(info, getBaseUrlProps(), dir); 518 519 info.setLocateKey(PROP_LINELE); 520 info.setLocateValue("0 0 F"); 521 info.setPlaceValue("ULEFT"); 522 523 String magKey = getPropValue(PROP_MAG, dir); 524 int lmag = 1; 525 int emag = 1; 526 StringTokenizer tok = new StringTokenizer(magKey); 527 lmag = (int) Misc.parseNumber((String) tok.nextElement()); 528 if (tok.hasMoreTokens()) { 529 emag = (int) Misc.parseNumber((String) tok.nextElement()); 530 } else { 531 emag = lmag; 532 } 533 info.setLineMag(lmag); 534 info.setElementMag(emag); 535 536 int lines = dir.getLines(); 537 int elems = dir.getElements(); 538 String sizeKey = getPropValue(PROP_SIZE, dir); 539 tok = new StringTokenizer(sizeKey); 540 String size = (String) tok.nextElement(); 541 if (!size.equalsIgnoreCase("all")) { 542 lines = (int) Misc.parseNumber(size); 543 if (tok.hasMoreTokens()) { 544 elems = (int) Misc.parseNumber((String) tok.nextElement()); 545 } else { 546 elems = lines; 547 } 548 } 549 info.setLines(lines); 550 info.setElements(elems); 551 /* 552 * System.out.println("url = " + info.getURLString().toLowerCase() + 553 * "\n"); 554 */ 555 return info; 556 } 557 558 /** 559 * Set the relative and absolute extra components. 560 */ 561// @Override protected JPanel makeTimesPanel() { 562// // show the time driver if the rest of the choosers are doing so. 563// JPanel timesPanel = 564// super.makeTimesPanel(false, true, getIdv().getUseTimeDriver()); 565// 566// // Make a new timesPanel that has extra components tacked on the 567// // bottom, inside the tabs 568// Component[] comps = timesPanel.getComponents(); 569// 570// if ((comps.length == 1) && (comps[0] instanceof JTabbedPane)) { 571// timesCardPanelExtra = new GuiUtils.CardLayoutPanel(); 572// timesCardPanelExtra.add(new JPanel(), "relative"); 573// timesCardPanelExtra.add(getExtraTimeComponent(), "absolute"); 574// timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra); 575// } 576// return timesPanel; 577// } 578 579 //The previous makeTimesPanel method was an override of makeTimesPanel() from AddeImageChooser.java 580 //Not sure if the if block in the previous method was working/required. Hence it has been left commented out 581 //The makeTimesPanel method below is an override of the makeTimesPanel() from TimesChooser.java 582 // and is introduced to include the Num Images text field [2793] -PM 583 @Override 584 protected JPanel makeTimesPanel() { 585 JPanel timesPanel = super.makeTimesPanel(false, true, getIdv().getUseTimeDriver()); 586 JPanel buttonPanel = new JPanel(new FlowLayout()); 587 buttonPanel.add(archiveDayBtn); 588 buttonPanel.add(new JLabel("Num Images: ")); 589 buttonPanel.add(imageCountTextField); 590 underTimelistPanel.add(BorderLayout.CENTER, buttonPanel); 591 return timesPanel; 592 } 593 594 /** 595 * Number of absolute times to list in the chooser. 596 * Must be a positive integer, or the word "ALL". 597 * Will throw up a dialog for invalid entries. 598 * 599 * @return 0 for valid entries, -1 for invalid 600 */ 601 private int parseImageCount() { 602 String countStr = imageCountTextField.getText(); 603 try { 604 int newCount = Integer.parseInt(countStr); 605 // Make sure it's reasonable 606 if (newCount > 0) { 607 int addeParam = 0 - newCount + 1; 608 numTimes = "" + addeParam; 609 } else { 610 throw new NumberFormatException(); 611 } 612 } catch (NumberFormatException nfe) { 613 // Still ok if they entered "ALL" 614 if (imageCountTextField.getText().isEmpty()) { 615 JOptionPane.showMessageDialog(this, 616 "Empty field, please enter a valid positive integer"); 617 return -1; 618 } 619 if (! imageCountTextField.getText().equalsIgnoreCase("all")) { 620 JOptionPane.showMessageDialog(this, 621 "Invalid entry: " + imageCountTextField.getText()); 622 return -1; 623 } 624 numTimes = imageCountTextField.getText(); 625 } 626 XmlObjectStore imgStore = getIdv().getStore(); 627 imgStore.put(PREF_NUM_IMAGE_PRESET_RADARCHOOSER, countStr); 628 imgStore.save(); 629 return 0; 630 } 631 632 /** 633 * Read the set of image times available for the current server/group/type 634 * This method is a wrapper, setting the wait cursor and wrapping the call 635 * to {@link #readTimesInner(boolean)}; in a try/catch block 636 */ 637 @Override public void readTimes() { 638 readTimes(false); 639 } 640 641 public void readTimes(boolean forceAll) { 642 643 // Make sure there is a valid entry in the image count text field 644 if (parseImageCount() < 0) return; 645 646 clearTimesList(); 647 if (!canReadTimes()) { 648 return; 649 } 650 Misc.run(new Runnable() { 651 public void run() { 652 updateStatus(); 653 showWaitCursor(); 654 try { 655 readTimesInner(forceAll); 656 checkSetNav(); 657 } catch (Exception e) { 658 handleConnectionError(e); 659 } 660 showNormalCursor(); 661 updateStatus(); 662 } 663 }); 664 } 665}