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