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.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.KeyStroke.getKeyStroke; 036import static javax.swing.LayoutStyle.ComponentPlacement.RELATED; 037 038import java.awt.BorderLayout; 039import java.awt.Color; 040import java.awt.FlowLayout; 041import java.awt.IllegalComponentStateException; 042import java.awt.Point; 043import java.awt.event.ActionEvent; 044import java.awt.event.ActionListener; 045import java.awt.event.KeyEvent; 046import java.awt.event.KeyListener; 047import java.text.SimpleDateFormat; 048import java.util.ArrayList; 049import java.util.Arrays; 050import java.util.Collections; 051import java.util.Date; 052import java.util.Enumeration; 053import java.util.Hashtable; 054import java.util.List; 055import java.util.SortedSet; 056import java.util.TreeSet; 057 058import javax.swing.GroupLayout; 059import javax.swing.JButton; 060import javax.swing.JComboBox; 061import javax.swing.JComponent; 062import javax.swing.JDialog; 063import javax.swing.JLabel; 064import javax.swing.JOptionPane; 065import javax.swing.JPanel; 066import javax.swing.KeyStroke; 067import javax.swing.ListSelectionModel; 068 069import org.joda.time.LocalDate; 070import org.slf4j.Logger; 071import org.slf4j.LoggerFactory; 072import org.w3c.dom.Element; 073 074import edu.wisc.ssec.mcidas.McIDASUtil; 075import edu.wisc.ssec.mcidas.adde.AddePointDataReader; 076import edu.wisc.ssec.mcidas.adde.DataSetInfo; 077import edu.wisc.ssec.mcidasv.ui.JCalendarDateEditor; 078import edu.wisc.ssec.mcidasv.ui.JCalendarPicker; 079import edu.wisc.ssec.mcidasv.ui.JTimeRangePicker; 080import edu.wisc.ssec.mcidasv.util.McVGuiUtils; 081import edu.wisc.ssec.mcidasv.util.McVGuiUtils.Width; 082 083import ucar.unidata.data.DataSelection; 084import ucar.unidata.data.AddeUtil; 085import ucar.unidata.data.point.AddePointDataSource; 086import ucar.unidata.idv.chooser.IdvChooserManager; 087import ucar.unidata.idv.chooser.adde.AddeServer; 088import ucar.unidata.ui.symbol.StationModel; 089import ucar.unidata.ui.symbol.StationModelManager; 090import ucar.unidata.util.GuiUtils; 091import ucar.unidata.util.Misc; 092import ucar.unidata.util.TwoFacedObject; 093import ucar.visad.UtcDate; 094 095import visad.DateTime; 096import visad.VisADException; 097 098/** 099 * Selection widget for ADDE point data 100 * 101 * @version $Revision$ $Date$ 102 */ 103 104public class AddePointDataChooser extends AddeChooser { 105 106 private static final long serialVersionUID = 1L; 107 108 /** Logging object. Use it! */ 109 private static final Logger logger = LoggerFactory.getLogger(AddePointDataChooser.class); 110 111 /** 112 * Property for the dataset name key. 113 * @see edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser#getDataSetName() 114 */ 115 public static String DATASET_NAME_KEY = "name"; 116 117 /** Property for the data type. */ 118 public static String DATA_TYPE = "ADDE.POINT.V"; 119 120 /** Are we currently reading times */ 121 private Object readTimesTask; 122 123 /** box and label for the relative time */ 124 protected JLabel relTimeIncLabel; 125 protected JComboBox relTimeIncBox; 126 127 /** the relative time increment */ 128 private float relativeTimeIncrement = 1.0f; 129 130 /** Date will default to current */ 131 DateTime dt = null; 132 133 /** archive day button and label */ 134 protected JLabel archiveDayLabel; 135 protected JButton archiveDayBtn; 136 137 /** archive date formatter */ 138 private SimpleDateFormat archiveDayFormatter; 139 140 /** station model manager */ 141 private StationModelManager stationModelManager; 142 143 /** allowed descriptor prefix */ 144 protected String descriptorsAllowPrefix = ""; 145 146 protected boolean firstTime = true; 147 protected boolean retry = true; 148 149 /** we reset the retry flag any time remote server changes */ 150 protected String previousServer = ""; 151 152 /** Possibly ask for times a second time if the first sampling doesn't get any */ 153 private boolean gotObs = false; 154 protected boolean tryWithoutSampling = false; 155 156 /** Julian Date formatter */ 157 private static SimpleDateFormat jdFormat = new SimpleDateFormat("yyyyDDD"); 158 159 /** 160 * Create a chooser for ADDE POINT data 161 * 162 * @param mgr The chooser manager 163 * @param root The chooser.xml node 164 */ 165 166 public AddePointDataChooser(IdvChooserManager mgr, Element root) { 167 super(mgr, root); 168 169 this.stationModelManager = getIdv().getStationModelManager(); 170 171 relTimeIncLabel = new JLabel(" Interval:"); 172 relTimeIncBox = new JComboBox(); 173 relTimeIncBox.setToolTipText("Set the increment between relative times"); 174 relTimeIncBox.addActionListener(new ActionListener() { 175 @Override public void actionPerformed(ActionEvent ae) { 176 JComboBox box = (JComboBox) ae.getSource(); 177 if (GuiUtils.anySelected(box)) { 178 setRelativeTimeIncrement(getRelBoxValue()); 179 } 180 } 181 }); 182 McVGuiUtils.setComponentWidth(relTimeIncBox, Width.ONEHALF); 183 184 descriptorsAllowPrefix = ""; 185 186 try { 187 dt = new DateTime(); 188 } catch (VisADException vade) { 189 vade.printStackTrace(); 190 } 191 192 archiveDayBtn = new JButton("Set Day"); 193 archiveDayBtn.addActionListener(e -> getArchiveDay()); 194 archiveDayBtn.setToolTipText("Select a specific day"); 195 196 // Initialize time range to full day 197 archiveBegTime = "00:00:00"; 198 archiveEndTime = "23:59:59"; 199 200 archiveDayLabel = new JLabel("Select day:"); 201 archiveDayFormatter = new SimpleDateFormat(UtcDate.IYD_FORMAT); 202 } 203 204 /** 205 * Do server connection stuff... override this with type-specific methods 206 */ 207 @Override protected void readFromServer() { 208 if (archiveDayLabel != null) { 209 archiveDayLabel.setText("Select day:"); 210 } 211 super.readFromServer(); 212 } 213 214 /** 215 * Generate a list of image descriptors for the descriptor list. 216 */ 217 @Override protected void readDescriptors() { 218 try { 219 220 String currentServer = this.getServer(); 221 logger.info("Current server: " + currentServer); 222 logger.info("Previous server: " + previousServer); 223 if (! currentServer.equals("previousServer")) { 224 // Reset retry flag to force archive servers to throw up date picker 225 logger.info("Resetting RETRY flag"); 226 retry = true; 227 } 228 previousServer = currentServer; 229 230 StringBuffer buff = getGroupUrl(REQ_DATASETINFO, getGroup()); 231 buff.append("&type=").append(getDataType()); 232 DataSetInfo dsinfo = new DataSetInfo(buff.toString()); 233 descriptorTable = dsinfo.getDescriptionTable(); 234 235 // Only show descriptorsAllowPrefix if set 236 for (Enumeration e = descriptorTable.keys(); e.hasMoreElements();) { 237 Object key = e.nextElement(); 238 String str = (String)descriptorTable.get(key); 239 if (!descriptorsAllowPrefix.isEmpty() && str.indexOf(descriptorsAllowPrefix) != 0) { 240 descriptorTable.remove(key); 241 } 242 } 243 244 String[] names = new String[descriptorTable.size()]; 245 Enumeration enumeration = descriptorTable.keys(); 246 for (int i = 0; enumeration.hasMoreElements(); i++) { 247 Object thisElement = enumeration.nextElement(); 248 if (!isLocalServer()) { 249 names[i] = descriptorTable.get(thisElement).toString() + nameSeparator + thisElement.toString(); 250 } else { 251 names[i] = thisElement.toString(); 252 } 253 } 254 Arrays.sort(names); 255 setDescriptors(names); 256 setState(STATE_CONNECTED); 257 } catch (Exception e) { 258 handleConnectionError(e); 259 } 260 } 261 262 /** 263 * Load in an ADDE point data set based on the {@code PropertyChangeEvent}. 264 */ 265 @Override public void doLoadInThread() { 266 showWaitCursor(); 267 try { 268 StationModel selectedStationModel = getSelectedStationModel(); 269 String source = getRequestUrl(); 270 271 // make properties Hashtable to hand the station name 272 // to the AddeProfilerDataSource 273 Hashtable ht = new Hashtable(); 274 getDataSourceProperties(ht); 275 ht.put(DataSelection.PROP_CHOOSERTIMEMATCHING, getDoTimeDrivers()); 276 ht.put(AddePointDataSource.PROP_STATIONMODELNAME, 277 selectedStationModel.getName()); 278 ht.put(DATASET_NAME_KEY, getDescriptor()); 279 ht.put(DATA_NAME_KEY, getDataName()); 280 if (source.contains(AddeUtil.RELATIVE_TIME)) { 281 ht.put(AddeUtil.NUM_RELATIVE_TIMES, getRelativeTimeIndices()); 282 ht.put(AddeUtil.RELATIVE_TIME_INCREMENT, getRelativeTimeIncrement()); 283 } 284 285 if (getDoAbsoluteTimes()) { 286 ht.put(AddeUtil.ABSOLUTE_TIMES, getSelectedAbsoluteTimes()); 287 } 288 289 makeDataSource(source, DATA_TYPE, ht); 290 saveServerState(); 291 } catch (Exception excp) { 292 logException("Unable to open ADDE point dataset", excp); 293 } 294 showNormalCursor(); 295 // uncheck the check box every time click the add source button 296 drivercbx.setSelected(false); 297 enableTimeWidgets(); 298 setDoTimeDrivers(false); 299 } 300 301 /** 302 * Show the archive dialog. This method is not meant to be called but is 303 * public by reason of implementation (or insanity). 304 */ 305 public void getArchiveDay() { 306 307 final JDialog dialog = GuiUtils.createDialog("Set Day and Time Range", true); 308 final JCalendarPicker picker = new JCalendarPicker(false); 309 final JTimeRangePicker trp = new JTimeRangePicker(); 310 311 if (archiveDay != null) { 312 if (archiveDayFormatter == null) { 313 archiveDayFormatter = new SimpleDateFormat(UtcDate.YMD_FORMAT); 314 } 315 Date d = null; 316 try { 317 d = archiveDayFormatter.parse(archiveDay); 318 picker.setDate(d); 319 } catch (Exception e) { 320 logException("parsing archive day " + archiveDay, e); 321 } 322 } 323 324 ActionListener listener = new ActionListener() { 325 public void actionPerformed(ActionEvent ae) { 326 String cmd = ae.getActionCommand(); 327 if (cmd.equals(GuiUtils.CMD_OK)) { 328 329 // bad time range, throw up error window 330 if (! trp.timeRangeOk()) { 331 String msg = "Time range is invalid.\n" + 332 "Please provide valid hours, minutes and\n" + 333 "seconds, with End Time > Start Time."; 334 Object[] params = { msg }; 335 JOptionPane.showMessageDialog(null, params, "Invalid Time Range", JOptionPane.OK_OPTION); 336 return; 337 } else { 338 archiveBegTime = trp.getBegTimeStr(); 339 archiveEndTime = trp.getEndTimeStr(); 340 } 341 try { 342 String pickerDate = picker.getUserSelectedDay(); 343 LocalDate ld = LocalDate.parse(pickerDate); 344 dt = new DateTime(ld.toDate()); 345 archiveDay = jdFormat.format(ld.toDate()); 346 archiveDayBtn.setText(pickerDate); 347 } catch (Exception e) { 348 } 349 350 setDoAbsoluteTimes(true); 351 clearTimesList(); 352 descriptorChanged(); 353 } 354 dialog.dispose(); 355 } 356 }; 357 358 final JCalendarDateEditor dateEditor = 359 (JCalendarDateEditor)picker.getDateChooser().getDateEditor(); 360 dateEditor.getUiComponent().addKeyListener(new KeyListener() { 361 @Override public void keyTyped(KeyEvent e) { } 362 363 @Override public void keyPressed(KeyEvent e) { } 364 365 @Override public void keyReleased(KeyEvent e) { 366 if (!Color.RED.equals(dateEditor.getForeground())) { 367 KeyStroke stroke = 368 getKeyStroke(e.getKeyCode(), e.getModifiers()); 369 if (stroke.getKeyCode() == KeyEvent.VK_ENTER) { 370 try { 371 String pickerDate = picker.getUserSelectedDay(); 372 LocalDate ld = LocalDate.parse(pickerDate); 373 dt = new DateTime(ld.toDate()); 374 archiveDay = jdFormat.format(ld.toDate()); 375 archiveDayBtn.setText(pickerDate); 376 } catch (Exception ex) { 377 // nothing to do 378 } 379 setDoAbsoluteTimes(true); 380 descriptorChanged(); 381 dialog.dispose(); 382 } 383 } 384 } 385 }); 386 387 JPanel buttons = GuiUtils.makeButtons(listener, new String[] { 388 GuiUtils.CMD_OK, GuiUtils.CMD_CANCEL }); 389 390 JPanel dateTimePanel = new JPanel(new FlowLayout()); 391 dateTimePanel.add(picker); 392 dateTimePanel.add(trp); 393 394 JComponent contents = GuiUtils.topCenterBottom(GuiUtils.inset(GuiUtils 395 .lLabel("Please select a day and optional time range for this dataset:"), 10), GuiUtils 396 .inset(dateTimePanel, 10), buttons); 397 Point p = new Point(200, 200); 398 if (archiveDayBtn != null) { 399 try { 400 p = archiveDayBtn.getLocationOnScreen(); 401 } catch (IllegalComponentStateException ice) { 402 } 403 } 404 dialog.setLocation(p); 405 dialog.getContentPane().add(contents); 406 dialog.pack(); 407 dialog.setVisible(true); 408 } 409 410 /** 411 * Get the selected station model. 412 * 413 * @return StationModel to use: defined by defaultModels list in ctor 414 */ 415 public StationModel getSelectedStationModel() { 416 StationModel returnModel = null; 417 if (isUpperAir()) { 418 returnModel = this.stationModelManager.getStationModel("Observations>Upper Air"); 419 } else if (isSynoptic()) { 420 returnModel = this.stationModelManager.getStationModel("Observations>SYNOP"); 421 } else { 422 returnModel = this.stationModelManager.getStationModel("Observations>METAR"); 423 } 424 return returnModel; 425 } 426 427 /** 428 * Add the interval selector to the component. 429 * @return superclass component with extra stuff 430 */ 431 @Override protected JPanel makeTimesPanel() { 432 JComponent extra1 = getExtraTimeComponentRelative(); 433 GuiUtils.enableTree(extra1, false); 434 JComponent extra2 = getExtraTimeComponentAbsolute(); 435 JPanel timesPanel = super.makeTimesPanel(extra1, extra2); 436 JPanel buttonPanel = new JPanel(new FlowLayout()); 437 buttonPanel.add(archiveDayBtn); 438 underTimelistPanel.add(BorderLayout.CENTER, buttonPanel); 439 return timesPanel; 440 } 441 442 /** 443 * Get the extra time widget, but built in a different way. 444 * Designed to be put into a GroupLayout 445 * 446 * @return Extra time widget 447 */ 448 protected JComponent getExtraTimeComponentRelative() { 449 TwoFacedObject[] intervals = { 450 new TwoFacedObject("30 minute", .5f), 451 new TwoFacedObject("Hourly", 1f), 452 new TwoFacedObject("Three hourly", 3f), 453 new TwoFacedObject("Six hourly", 6f), 454 new TwoFacedObject("12 hourly", 12f), 455 new TwoFacedObject("24 hourly", 24f) 456 }; 457 458 GuiUtils.setListData(relTimeIncBox, intervals); 459 if (relTimeIncBox.getItemCount()>=2) relTimeIncBox.setSelectedIndex(1); 460 461 return McVGuiUtils.makeLabeledComponent(relTimeIncLabel, relTimeIncBox, McVGuiUtils.Position.LEFT); 462 } 463 464 /** 465 * Overridden in McIDAS-V to get a nicer set of interval combo box options. 466 * 467 * @return {@code JPanel} containing a label and the interval combo box. 468 */ 469 @Override protected JComponent getExtraRelativeTimeComponent() { 470 return getExtraTimeComponentRelative(); 471 } 472 473 /** 474 * Get the time popup widget 475 * 476 * @return a widget for selecing the day 477 */ 478 protected JComponent getExtraTimeComponentAbsolute() { 479 return null; 480// return McVGuiUtils.makeLabeledComponent(archiveDayLabel, archiveDayBtn); 481 } 482 483 /** 484 * Get the value from the relative increment box 485 * 486 * @return the selected value or a default 487 */ 488 protected float getRelBoxValue() { 489 float value = relativeTimeIncrement; 490 if (relTimeIncBox != null) { 491 Object o = relTimeIncBox.getSelectedItem(); 492 if (o != null) { 493 String val = TwoFacedObject.getIdString(o); 494 value = (float) Misc.parseNumber(val); 495 } 496 } 497 return value; 498 } 499 500 /** 501 * Get the string from the relative increment box 502 * 503 * @return the selected string or a default 504 */ 505 public String getRelBoxString() { 506 String value = ""; 507 if (relTimeIncBox != null) { 508 Object o = relTimeIncBox.getSelectedItem(); 509 if (o != null) { 510 value = TwoFacedObject.getIdString(o); 511 } 512 } 513 return value; 514 } 515 516 /** 517 * Get the request URL 518 * 519 * @return the request URL 520 */ 521 public String getRequestUrl() { 522 StringBuffer request = getGroupUrl(REQ_POINTDATA, getGroup()); 523 appendKeyValue(request, PROP_USER, getLastAddedUser()); 524 appendKeyValue(request, PROP_PROJ, getLastAddedProj()); 525 appendKeyValue(request, PROP_DESCR, getDescriptor()); 526 appendRequestSelectClause(request); 527 appendKeyValue(request, PROP_NUM, "ALL"); 528 // TJJ Dec 2020 - looks like POS = 0 limits requests to current day, so 529 // we should always use ALL, and let the rel/abs filters dictate what is 530 // delivered? 531 // appendKeyValue(request, PROP_POS, getDoRelativeTimes() ? "ALL" : "0"); 532 appendKeyValue(request, PROP_POS, "ALL"); 533 logger.info("Request URL: " + request.toString()); 534 return request.toString(); 535 } 536 537 /** 538 * Get the select clause for the ADDE request specific to this 539 * type of data. 540 * 541 * @param buf The buffer to append to 542 */ 543 544 protected void appendRequestSelectClause(StringBuffer buf) { 545 StringBuilder selectValue = new StringBuilder(1024); 546 selectValue.append('\''); 547 selectValue.append(getDayTimeSelectString()); 548 // TODO: why is SFCHOURLY explicit here? better way to do it? 549 if ("SFCHOURLY".equalsIgnoreCase(getDescriptor())) { 550 selectValue.append(";TYPE 0"); 551 } 552 selectValue.append(';'); 553 554 if (isUpperAir()){ 555 selectValue.append(AddeUtil.LEVEL); 556 selectValue.append(';'); 557 } 558 // TJJ - This must be a placeholder macro for the URL 559 selectValue.append(AddeUtil.LATLON_BOX); 560 selectValue.append('\''); 561 appendKeyValue(buf, PROP_SELECT, selectValue.toString()); 562 } 563 564 /** 565 * Check if we are ready to read times 566 * 567 * @return true if times can be read 568 */ 569 protected boolean canReadTimes() { 570 return haveDescriptorSelected(); 571 } 572 573 /** 574 * Enable or disable the GUI widgets based on what has been 575 * selected. 576 */ 577 @Override protected void enableWidgets() { 578 boolean descriptorState = ((getState() == STATE_CONNECTED) 579 && canReadTimes()); 580 581 for (int i = 0; i < compsThatNeedDescriptor.size(); i++) { 582 JComponent comp = (JComponent) compsThatNeedDescriptor.get(i); 583 GuiUtils.enableTree(comp, descriptorState); 584 } 585 586 boolean timesOk = timesOk(); 587 588 // Require times to be selected 589 GuiUtils.enableTree(loadButton, descriptorState && timesOk); 590 591 checkTimesLists(); 592 593 enableAbsoluteTimesList(getDoAbsoluteTimes() && descriptorState); 594 595 getRelativeTimesChooser().setEnabled( !getDoAbsoluteTimes() 596 && descriptorState); 597 598 if (drivercbx != null) { 599// logger.trace("set drivercbx={}", anyTimeDrivers() && descriptorState); 600 drivercbx.setEnabled(anyTimeDrivers() && descriptorState); 601 } 602 603 revalidate(); 604 } 605 606 /** 607 * Do we have times selected. Either we are doing absolute 608 * times and there are some selected in the list. Or we 609 * are doing relative times and we have done a connect to the 610 * server 611 * 612 * @return Do we have times 613 */ 614 public boolean timesOk() { 615 if (getDoAbsoluteTimes() && !haveTimeSelected()) { 616 return false; 617 } 618 return true; 619 } 620 621 /** 622 * Return {@code true} if selected descriptor is for SYNOPTIC data. 623 * 624 * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()} 625 * is {@literal "SYNOP"}. 626 */ 627 protected boolean isSynoptic() { 628 return "SYNOP".equals(getDescriptor()); 629 } 630 631 /** 632 * Return {@code true} if selected descriptor is for upper air. 633 * 634 * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()} 635 * is {@literal "UPPERMAND"}. 636 */ 637 protected boolean isUpperAir() { 638 return "UPPERMAND".equals(getDescriptor()); 639 640 } 641 642 /** 643 * Return {@code true} if selected descriptor is for profiler. 644 * 645 * @return {@code true} iff {@link edu.wisc.ssec.mcidasv.chooser.adde.AddePointDataChooser#getDescriptor()} 646 * is {@literal "PROF"}. 647 */ 648 protected boolean isProfiler() { 649 return "PROF".equals(getDescriptor()); 650 } 651 652 /** 653 * Update the widget with the latest data. 654 * 655 * @throws Exception On badness 656 */ 657 @Override public void handleUpdate() throws Exception { 658 if (getState() != STATE_CONNECTED) { 659 //If not connected then update the server list 660 updateServerList(); 661 } else { 662 //If we are already connected then update the rest of the chooser 663 descriptorChanged(); 664 } 665 updateStatus(); 666 } 667 668 /** 669 * Get the request string for times particular to this chooser 670 * 671 * @return request string 672 */ 673 protected String getTimesRequest() { 674 StringBuffer buf = getGroupUrl(REQ_POINTDATA, getGroup()); 675 appendKeyValue(buf, PROP_USER, getLastAddedUser()); 676 appendKeyValue(buf, PROP_PROJ, getLastAddedProj()); 677 appendKeyValue(buf, PROP_DESCR, getDescriptor()); 678 // TJJ - I don't know what is up with these hardcoded CONUS-like 679 // bounds, but finding it's best if I just leave them in 680 if (! isUpperAir() && ! tryWithoutSampling) { 681 appendKeyValue(buf, PROP_POS, "ALL"); 682 appendKeyValue(buf, PROP_SELECT, "'DAY " + getJulianDay() + 683 ";TIME " + archiveBegTime + " " + archiveEndTime + 684 ";LAT 38 42;LON 70 75'"); 685 } 686 else { 687 appendKeyValue(buf, PROP_SELECT, "'DAY " + getJulianDay() + 688 ";TIME " + archiveBegTime + " " + archiveEndTime + 689 ";LAT 38 42;LON 70 75'"); 690 appendKeyValue(buf, PROP_POS, "ALL"); 691 } 692 if (getDoAbsoluteTimes()) { 693 appendKeyValue(buf, PROP_NUM, "ALL"); 694 } 695 696 appendKeyValue(buf, PROP_PARAM, "DAY TIME"); 697 return buf.toString(); 698 } 699 700 /** 701 * Get the current, or archive, if selected, Julian day as a String 702 * 703 * @return the julian day as a string (yyyyDDD) 704 */ 705 706 private String getJulianDay() { 707 return UtcDate.formatUtcDate(dt, "yyyyDDD"); 708 } 709 710 /** 711 * This allows derived classes to provide their own name for labeling, etc. 712 * 713 * @return the dataset name 714 */ 715 @Override public String getDataName() { 716 return "Point Data"; 717 } 718 719 /** 720 * _more_ 721 */ 722 @Override public void doCancel() { 723 readTimesTask = null; 724 setState(STATE_UNCONNECTED); 725 super.doCancel(); 726 } 727 728 /** locking mutex */ 729 private final Object MUTEX = new Object(); 730 731 /** 732 * Read the set of image times available for the current server/group/type 733 * This method is a wrapper, setting the wait cursor and wrapping the 734 * call to {@link #readTimesInner()}; in a try/catch block 735 */ 736 @Override public void readTimes() { 737 clearTimesList(); 738 if (!canReadTimes()) { 739 return; 740 } 741 Misc.run(new Runnable() { 742 @Override public void run() { 743 updateStatus(); 744 showWaitCursor(); 745 try { 746 gotObs = false; 747 tryWithoutSampling = false; 748 readTimesInner(); 749 // Try again, this time not sampling by LAT/LON 750 if (haveDescriptorSelected() && !gotObs) { 751 tryWithoutSampling = true; 752 readTimesInner(); 753 } 754 } catch (Exception e) { 755 handleConnectionError(e); 756 } 757 showNormalCursor(); 758 updateStatus(); 759 } 760 }); 761 } 762 763 /** 764 * Set the list of dates/times based on the image selection 765 */ 766 protected void readTimesInner() { 767 SortedSet<DateTime> uniqueTimes = Collections.synchronizedSortedSet(new TreeSet<DateTime>()); 768 769 readTimesTask = startTask(); 770 updateStatus(); 771 Object task = readTimesTask; 772 try { 773 AddePointDataReader apr = new AddePointDataReader(getTimesRequest()); 774 //Make sure no other loads are occurred 775 boolean ok = stopTaskAndIsOk(task); 776 if (!Misc.equals(readTimesTask, task) || !ok) { 777 return; 778 } 779 readTimesTask = null; 780 781 synchronized (MUTEX) { 782 int[][] data = apr.getData(); 783 String[] units = apr.getUnits(); 784 if ( !"CYD".equals(units[0]) || !"HMS".equals(units[1])) { 785 throw new Exception("can't handle date/time units"); 786 } 787 int numObs = data[0].length; 788 //System.out.println("found " + numObs + " obs"); 789 // loop through and find all the unique times 790 try { 791 for (int i = 0; i < numObs; i++) { 792 DateTime dt = 793 new DateTime(McIDASUtil.mcDayTimeToSecs(data[0][i], 794 data[1][i])); 795 uniqueTimes.add(dt); 796 } 797 } catch (Exception e) { 798 logger.error("problem building list of unique times", e); 799 } 800 //System.out.println( 801 // "found " + uniqueTimes.size() + " unique times"); 802 if (getDoAbsoluteTimes()) { 803 if (!uniqueTimes.isEmpty()) { 804 setAbsoluteTimes(new ArrayList<DateTime>(uniqueTimes)); 805 getTimesList().setSelectionMode(ListSelectionModel.SINGLE_INTERVAL_SELECTION); 806 } 807 808 // Select the last n hours 809 int selectedIndex = getAbsoluteTimes().size() - 1; 810 int firstIndex = Math.max(0, selectedIndex 811 - getDefaultRelativeTimeIndex()); 812 if (selectedIndex >= 0) 813 setSelectedAbsoluteTime(selectedIndex, firstIndex); 814 } 815 if (numObs>0) { 816 gotObs = true; 817 } 818 } 819 setState(STATE_CONNECTED); 820 } catch (Exception excp) { 821 stopTask(task); 822 readTimesTask = null; 823 handleConnectionError(excp); 824 if (! retry) { 825 return; 826 } 827 try { 828 handleUpdate(); 829 } catch (Exception e) { 830 logger.error("problem handling update", e); 831 } 832 } 833 } 834 835 /** 836 * Show the given error to the user. If it was an Adde exception 837 * that was a bad server error then print out a nice message. 838 * 839 * @param e The exception 840 */ 841 842 @Override protected void handleConnectionError(Exception e) { 843 if (retry) { 844 // initialize Archive Date to PREVIOUS DAY (archive server) 845 LocalDate ld = LocalDate.now(); 846 ld = ld.minusDays(1); 847 archiveDay = jdFormat.format(ld.toDate()); 848 logger.info("setting archiveDay to: " + archiveDay); 849 getArchiveDay(); 850 retry = false; 851 return; 852 } 853 super.handleConnectionError(e); 854 } 855 856 /** 857 * Are there any times selected. 858 * 859 * @return Any times selected. 860 */ 861 @Override protected boolean haveTimeSelected() { 862 return !getDoAbsoluteTimes() || getHaveAbsoluteTimesSelected(); 863 } 864 865 /** 866 * Create the date time selection string for the "select" clause 867 * of the ADDE URL. 868 * 869 * @return the select day and time strings 870 */ 871 protected String getDayTimeSelectString() { 872 StringBuilder buf = new StringBuilder(1024); 873 if (getDoAbsoluteTimes()) { 874 List times = getSelectedAbsoluteTimes(); 875 876 // no time selection is permitted as a valid choice - 877 // will then use all times today by default. 878 if (times.isEmpty()) { 879 return ""; 880 } 881 882 //check for the "no times available" message 883 if (times.get(0) instanceof String) { 884 return ""; 885 } 886 887 if (archiveDay != null) { 888 logger.trace("archiveDay: {}", archiveDay); 889 try { 890 buf.append("DAY ").append(archiveDay).append(';'); 891 } catch (Exception e) { 892 logger.error("archiveDay parse error", e); 893 } 894 } 895 else { 896 // TJJ - I don't think this will happen any more, but if I'm wrong, 897 // set the value to current day and pass that in the SELECT clause 898 logger.trace("archiveDay is null!"); 899 LocalDate ld = LocalDate.now(); 900 try { 901 dt = new DateTime(ld.toDate()); 902 } catch (VisADException e) { 903 e.printStackTrace(); 904 } 905 archiveDay = jdFormat.format(ld.toDate()); 906 logger.trace("Setting archiveDay to: " + archiveDay); 907 try { 908 buf.append("DAY ").append(archiveDay).append(';'); 909 } catch (Exception e) { 910 logger.error("archiveDay parse error", e); 911 } 912 } 913 914 buf.append("TIME "); 915 for (int i = 0; i < times.size(); i++) { 916 DateTime dt = (DateTime) times.get(i); 917 buf.append(UtcDate.getHMS(dt)); 918 if (i != times.size() - 1) { 919 buf.append(','); 920 } 921 } 922 } else { 923 buf.append(AddeUtil.RELATIVE_TIME); 924 } 925 return buf.toString(); 926 } 927 928 /** 929 * Get the data type for this chooser 930 * 931 * @return the type 932 */ 933 @Override public String getDataType() { 934 return "POINT"; 935 } 936 937 /** 938 * Get the increment between times for relative time requests 939 * 940 * @return time increment (hours) 941 */ 942 @Override public float getRelativeTimeIncrement() { 943 return relativeTimeIncrement; 944 } 945 946 /** 947 * Set the increment between times for relative time requests 948 * 949 * @param increment time increment (hours) 950 */ 951 public void setRelativeTimeIncrement(float increment) { 952 relativeTimeIncrement = increment; 953 if (relTimeIncBox != null) { 954 relTimeIncBox.setSelectedItem(relativeTimeIncrement); 955 } 956 } 957 958 /** 959 * Update labels, enable widgets, etc. 960 */ 961 @Override protected void updateStatus() { 962 super.updateStatus(); 963 if (readTimesTask != null) { 964 if (taskOk(readTimesTask)) { 965 setStatus("Reading available times from server"); 966 } 967 } else if (getDoAbsoluteTimes() && !haveTimeSelected()) { 968 setStatus(MSG_TIMES); 969 } 970 enableWidgets(); 971 } 972 973 974 /** 975 * Get the descriptor widget label. 976 * 977 * @return label for the descriptor widget 978 */ 979 @Override public String getDescriptorLabel() { 980 return "Point Type"; 981 } 982 983 /** 984 * get the ADDE server group type to use 985 * 986 * @return group type 987 */ 988 @Override protected String getGroupType() { 989 return AddeServer.TYPE_POINT; 990 } 991 992 /** 993 * Make the UI for this selector. 994 * 995 * @return The gui 996 */ 997 @Override public JComponent doMakeContents() { 998 JPanel myPanel = new JPanel(); 999 1000 McVGuiUtils.setComponentWidth(descriptorComboBox, 584); 1001 1002 JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:"); 1003 addServerComp(stationLabel); 1004 1005 JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:"); 1006 addDescComp(timesLabel); 1007 1008 JPanel timesPanel = makeTimesPanel(); 1009 timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder()); 1010 addDescComp(timesPanel); 1011 1012 GroupLayout layout = new GroupLayout(myPanel); 1013 myPanel.setLayout(layout); 1014 layout.setHorizontalGroup( 1015 layout.createParallelGroup(LEADING) 1016 .addGroup(layout.createSequentialGroup() 1017 .addGroup(layout.createParallelGroup(LEADING) 1018 .addGroup(layout.createSequentialGroup() 1019 .addComponent(descriptorLabel) 1020 .addGap(GAP_RELATED) 1021 .addComponent(descriptorComboBox)) 1022 .addGroup(layout.createSequentialGroup() 1023 .addComponent(timesLabel) 1024 .addGap(GAP_RELATED) 1025 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))) 1026 ); 1027 layout.setVerticalGroup( 1028 layout.createParallelGroup(LEADING) 1029 .addGroup(layout.createSequentialGroup() 1030 .addGroup(layout.createParallelGroup(BASELINE) 1031 .addComponent(descriptorLabel) 1032 .addComponent(descriptorComboBox)) 1033 .addPreferredGap(RELATED) 1034 .addGroup(layout.createParallelGroup(LEADING) 1035 .addComponent(timesLabel) 1036 .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))) 1037 ); 1038 1039 setInnerPanel(myPanel); 1040 return super.doMakeContents(); 1041 } 1042 1043 public JComponent doMakeContents(boolean doesOverride) { 1044 if (doesOverride) { 1045 return super.doMakeContents(); 1046 } else { 1047 return doMakeContents(); 1048 } 1049 } 1050}