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.display.hydra; 030 031import java.awt.Color; 032import java.awt.Component; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.io.File; 036import java.io.FileWriter; 037import java.io.PrintWriter; 038import java.rmi.RemoteException; 039import java.util.ArrayList; 040import java.util.Enumeration; 041import java.util.HashMap; 042import java.util.Hashtable; 043import java.util.List; 044import java.util.Map; 045 046import javax.swing.JComboBox; 047import javax.swing.JOptionPane; 048 049import org.python.antlr.ast.Str; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052 053import visad.CellImpl; 054import visad.ConstantMap; 055import visad.DataReference; 056import visad.DataReferenceImpl; 057import visad.Display; 058import visad.DisplayEvent; 059import visad.DisplayListener; 060import visad.FlatField; 061import visad.FunctionType; 062import visad.Gridded1DSet; 063import visad.Gridded2DSet; 064import visad.LocalDisplay; 065import visad.Real; 066import visad.RealTuple; 067import visad.RealTupleType; 068import visad.RealType; 069import visad.ScalarMap; 070import visad.VisADException; 071import visad.bom.RubberBandBoxRendererJ3D; 072 073import ucar.unidata.data.DirectDataChoice; 074import ucar.unidata.idv.ViewManager; 075import ucar.unidata.util.LogUtil; 076import ucar.visad.display.DisplayableData; 077import ucar.visad.display.XYDisplay; 078 079import edu.wisc.ssec.mcidasv.control.HydraCombo; 080import edu.wisc.ssec.mcidasv.control.HydraControl; 081import edu.wisc.ssec.mcidasv.control.LinearCombo; 082import edu.wisc.ssec.mcidasv.control.MultiSpectralControl; 083import edu.wisc.ssec.mcidasv.data.HydraDataSource; 084import edu.wisc.ssec.mcidasv.data.hydra.GrabLineRendererJ3D; 085import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable; 086import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset; 087import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData; 088import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralDataSource; 089import edu.wisc.ssec.mcidasv.data.hydra.SuomiNPPDataSource; 090 091public class MultiSpectralDisplay implements DisplayListener { 092 093 private static final Logger logger = LoggerFactory.getLogger(MultiSpectralDisplay.class); 094 095 private static final String DISP_NAME = "Spectrum"; 096 private static int cnt = 1; 097 098 private DirectDataChoice dataChoice; 099 100 private ViewManager viewManager; 101 102 private float[] initialRangeX; 103 private float[] initialRangeY = { 180f, 320f }; 104 105 private RealType domainType; 106 private RealType rangeType; 107 private RealType uniqueRangeType; 108 109 private ScalarMap xmap; 110 private ScalarMap ymap; 111 112 private LocalDisplay display; 113 114 private FlatField image; 115 116 private FlatField spectrum = null; 117 118 private boolean imageExpired = true; 119 120 private MultiSpectralData data; 121 122 private float waveNumber; 123 124 private List<DataReference> displayedThings = new ArrayList<>(); 125 private HashMap<String, DataReference> idToRef = new HashMap<>(); 126 private HashMap<DataReference, ConstantMap[]> colorMaps = new HashMap<>(); 127 128 private HydraControl displayControl; 129 130 private DisplayableData imageDisplay = null; 131 132 private XYDisplay master; 133 134 private Gridded1DSet domainSet; 135 136 private JComboBox bandSelectComboBox = null; 137 138 public MultiSpectralDisplay(final HydraControl control) 139 throws VisADException, RemoteException 140 { 141 displayControl = control; 142 dataChoice = (DirectDataChoice)displayControl.getDataChoice(); 143 144 init(); 145 // writeToCSV(); 146 } 147 148 public MultiSpectralDisplay(final DirectDataChoice dataChoice) 149 throws VisADException, RemoteException 150 { 151 this.dataChoice = dataChoice; 152 init(); 153 } 154 155 // TODO: generalize this so that you can grab the image data for any 156 // channel 157 public FlatField getImageData() { 158 try { 159 if ((imageExpired) || (image == null)) { 160 imageExpired = false; 161 162 MultiDimensionSubset select = null; 163 Hashtable table = dataChoice.getProperties(); 164 Enumeration keys = table.keys(); 165 while (keys.hasMoreElements()) { 166 Object key = keys.nextElement(); 167 if (key instanceof MultiDimensionSubset) { 168 select = (MultiDimensionSubset) table.get(key); 169 } 170 } 171 Map<String, double[]> subset = select.getSubset(); 172 image = data.getImage(waveNumber, subset); 173 image = changeRangeType(image, uniqueRangeType); 174 } 175 } catch (Exception e) { 176 LogUtil.logException("MultiSpectralDisplay.getImageData", e); 177 } 178 179 return image; 180 } 181 182 public FlatField getImageDataFrom(final float channel) { 183 FlatField imageData = null; 184 try { 185 MultiDimensionSubset select = null; 186 Hashtable table = dataChoice.getProperties(); 187 Enumeration keys = table.keys(); 188 while (keys.hasMoreElements()) { 189 Object key = keys.nextElement(); 190 if (key instanceof MultiDimensionSubset) { 191 select = (MultiDimensionSubset) table.get(key); 192 } 193 } 194 Map<String, double[]> subset = select.getSubset(); 195 imageData = data.getImage(channel, subset); 196 uniqueRangeType = RealType.getRealType(rangeType.getName()+"_"+cnt++); 197 imageData = changeRangeType(imageData, uniqueRangeType); 198 } catch (Exception e) { 199 LogUtil.logException("MultiSpectralDisplay.getImageDataFrom", e); 200 } 201 return imageData; 202 } 203 204 private FlatField changeRangeType(FlatField image, RealType newRangeType) throws VisADException, RemoteException { 205 FunctionType ftype = (FunctionType)image.getType(); 206 FlatField new_image = new FlatField( 207 new FunctionType(ftype.getDomain(), newRangeType), image.getDomainSet()); 208 new_image.setSamples(image.getFloats(false), false); 209 return new_image; 210 } 211 212 public XYDisplay getMaster() { 213 return master; 214 } 215 216 public LocalDisplay getDisplay() { 217 return display; 218 } 219 220 public Component getDisplayComponent() { 221 return master.getDisplayComponent(); 222 } 223 224 public RealType getDomainType() { 225 return domainType; 226 } 227 228 public RealType getRangeType() { 229 return rangeType; 230 } 231 232 public ViewManager getViewManager() { 233 return viewManager; 234 } 235 236 public MultiSpectralData getMultiSpectralData() { 237 return data; 238 } 239 240 public Gridded1DSet getDomainSet() { 241 return domainSet; 242 } 243 244 private void init() throws VisADException, RemoteException { 245 246 HydraDataSource source = 247 (HydraDataSource) dataChoice.getDataSource(); 248 249 // TODO revisit this, may want to move method up to base class HydraDataSource 250 if (source instanceof SuomiNPPDataSource) { 251 data = ((SuomiNPPDataSource) source).getMultiSpectralData(dataChoice); 252 } 253 254 if (source instanceof MultiSpectralDataSource) { 255 data = ((MultiSpectralDataSource) source).getMultiSpectralData(dataChoice); 256 } 257 258 waveNumber = data.init_wavenumber; 259 260 try { 261 spectrum = data.getSpectrum(new int[] { 1, 1 }); 262 } catch (Exception e) { 263 LogUtil.logException("MultiSpectralDisplay.init", e); 264 } 265 266 domainSet = (Gridded1DSet)spectrum.getDomainSet(); 267 initialRangeX = getXRange(domainSet); 268 initialRangeY = data.getDataRange(); 269 270 domainType = getDomainType(spectrum); 271 rangeType = getRangeType(spectrum); 272 273 master = new XYDisplay(DISP_NAME, domainType, rangeType); 274 275 setDisplayMasterAttributes(master); 276 277 // set up the x- and y-axis 278 xmap = new ScalarMap(domainType, Display.XAxis); 279 ymap = new ScalarMap(rangeType, Display.YAxis); 280 281 xmap.setRange(initialRangeX[0], initialRangeX[1]); 282 ymap.setRange(initialRangeY[0], initialRangeY[1]); 283 284 display = master.getDisplay(); 285 display.addMap(xmap); 286 display.addMap(ymap); 287 display.addDisplayListener(this); 288 289 new RubberBandBox(this, xmap, ymap); 290 291 if (displayControl == null) { //- add in a ref for the default spectrum, ie no DisplayControl 292 DataReferenceImpl spectrumRef = new DataReferenceImpl(hashCode() + "_spectrumRef"); 293 spectrumRef.setData(spectrum); 294 addRef(spectrumRef, Color.WHITE); 295 } 296 297 if (data.hasBandNames()) { 298 bandSelectComboBox = new JComboBox(data.getBandNames().toArray()); 299 bandSelectComboBox.setSelectedItem(data.init_bandName); 300 bandSelectComboBox.addActionListener(new ActionListener() { 301 public void actionPerformed(ActionEvent e) { 302 String bandName = (String)bandSelectComboBox.getSelectedItem(); 303 if (bandName == null) 304 return; 305 306 Map<String, Float> bandMap = data.getBandNameMap(); 307 if (bandMap == null) 308 return; 309 310 if (!bandMap.containsKey(bandName)) 311 return; 312 313 setWaveNumber(bandMap.get(bandName)); 314 } 315 }); 316 } 317 } 318 319 /** 320 * McIDAS Inquiry #2535-3141 321 * Get multispectral data as a string for CSV files 322 * @return data as a comma separated string 323 */ 324 public String getDataAsString() { 325 try { 326 domainSet = (Gridded1DSet) spectrum.getDomainSet(); 327 328 int[] idx = new int[domainSet.getLength()]; 329 330 for (int i = 0; i < domainSet.getLength(); i++) {idx[i] = i;} 331 332 float[][] out = domainSet.indexToValue(idx); 333 double[][] out2 = spectrum.unpackValues(); 334 335 StringBuilder build = new StringBuilder(); 336 337 build.append(domainType.toString()); 338 build.append(","); 339 build.append(rangeType.toString()); 340 build.append("\n"); 341 342 for (int i = 0; i < out[0].length; i++) { 343 build.append(out[0][i]); 344 build.append(","); 345 build.append(out2[0][i]); 346 build.append("\n"); 347 } 348 349 return build.toString(); 350 351 } catch (Exception e) { 352 logger.warn("Writing to CSV failed", e); 353 return "NO STRING CREATED"; 354 } 355 } 356 357 public JComboBox getBandSelectComboBox() { 358 return bandSelectComboBox; 359 } 360 361 // TODO: HACK!! 362 public void setDisplayControl(final HydraControl control) { 363 displayControl = control; 364 } 365 366 public void displayChanged(final DisplayEvent e) throws VisADException, RemoteException { 367 // TODO: write a method like isChannelUpdate(EVENT_ID)? or maybe just 368 // deal with a super long if-statement and put an "OR MOUSE_RELEASED" 369 // up here? 370 if (e.getId() == DisplayEvent.MOUSE_RELEASED_CENTER) { 371 // McIDAS Inquiry #1057-3141 372 float val = getSelectorValue(channelSelector); 373 setWaveNumber(val); 374 if (displayControl != null) 375 displayControl.handleChannelChange(val); 376 } 377 else if (e.getId() == DisplayEvent.MOUSE_PRESSED_LEFT) { 378 if (e.getInputEvent().isControlDown()) { 379 xmap.setRange(initialRangeX[0], initialRangeX[1]); 380 ymap.setRange(initialRangeY[0], initialRangeY[1]); 381 } 382 } 383 else if (e.getId() == DisplayEvent.MOUSE_RELEASED) { 384 float val = getSelectorValue(channelSelector); 385 if (val != waveNumber) { 386 // TODO: setWaveNumber needs to be rethought, as it calls 387 // setSelectorValue which is redundant in the cases of dragging 388 // or clicking 389 setWaveNumber(val); 390 if (displayControl != null) 391 displayControl.handleChannelChange(val); 392 } 393 } 394 } 395 396 public DisplayableData getImageDisplay() { 397 if (imageDisplay == null) { 398 try { 399 uniqueRangeType = RealType.getRealType(rangeType.getName()+"_"+cnt++); 400 imageDisplay = new HydraRGBDisplayable("image", uniqueRangeType, null, true, displayControl); 401 } catch (Exception e) { 402 LogUtil.logException("MultiSpectralDisplay.getImageDisplay", e); 403 } 404 } 405 return imageDisplay; 406 } 407 408 public float getWaveNumber() { 409 return waveNumber; 410 } 411 412 public int getChannelIndex() throws Exception { 413 return data.getChannelIndexFromWavenumber(waveNumber); 414 } 415 416 public void refreshDisplay() throws VisADException, RemoteException { 417 if (display == null) 418 return; 419 420 synchronized (displayedThings) { 421 for (DataReference ref : displayedThings) { 422 display.removeReference(ref); 423 display.addReference(ref, colorMaps.get(ref)); 424 } 425 } 426 } 427 428 public boolean hasNullData() { 429 try { 430 synchronized (displayedThings) { 431 for (DataReference ref : displayedThings) { 432 if (ref.getData() == null) 433 return true; 434 } 435 } 436 } catch (Exception e) { } 437 return false; 438 } 439 440 /** ID of the selector that controls the displayed channel. */ 441 private final String channelSelector = hashCode() + "_chanSelect"; 442 443 /** The map of selector IDs to selectors. */ 444 private final Map<String, DragLine> selectors = 445 new HashMap<String, DragLine>(); 446 447 public void showChannelSelector() { 448 try { 449 createSelector(channelSelector, Color.GREEN); 450 } catch (Exception e) { 451 LogUtil.logException("MultiSpectralDisplay.showChannelSelector", e); 452 } 453 } 454 455 public void hideChannelSelector() { 456 try { 457 DragLine selector = removeSelector(channelSelector); 458 selector = null; 459 } catch (Exception e) { 460 LogUtil.logException("MultiSpectralDisplay.hideChannelSelector", e); 461 } 462 } 463 464 public DragLine createSelector(final String id, final Color color) throws Exception { 465 if (id == null) 466 throw new NullPointerException("selector id cannot be null"); 467 if (color == null) 468 throw new NullPointerException("selector color cannot be null"); 469 return createSelector(id, makeColorMap(color)); 470 } 471 472 public DragLine createSelector(final String id, final ConstantMap[] color) throws Exception { 473 if (id == null) 474 throw new NullPointerException("selector id cannot be null"); 475 if (color == null) 476 throw new NullPointerException("selector color cannot be null"); 477 478 if (selectors.containsKey(id)) 479 return selectors.get(id); 480 481 DragLine selector = new DragLine(this, id, color, initialRangeY); 482 selector.setHydraControl(displayControl); 483 selector.setSelectedValue(waveNumber); 484 selectors.put(id, selector); 485 return selector; 486 } 487 488 public DragLine getSelector(final String id) { 489 return selectors.get(id); 490 } 491 492 public float getSelectorValue(final String id) { 493 DragLine selector = selectors.get(id); 494 if (selector == null) 495 return Float.NaN; 496 return selector.getSelectedValue(); 497 } 498 499 public void setSelectorValue(final String id, final float value) 500 throws VisADException, RemoteException 501 { 502 DragLine selector = selectors.get(id); 503 if (selector != null) 504 selector.setSelectedValue(value); 505 } 506 507 // BAD BAD BAD BAD 508 public void updateControlSelector(final String id, final float value) { 509 if (displayControl == null) 510 return; 511 if (displayControl instanceof LinearCombo) { 512 ((LinearCombo)displayControl).updateSelector(id, value); 513 } else if (displayControl instanceof HydraCombo) { 514 ((HydraCombo)displayControl).updateComboPanel(id, value); 515 } 516 } 517 518 public DragLine removeSelector(final String id) { 519 DragLine selector = selectors.remove(id); 520 if (selector == null) 521 return null; 522 selector.annihilate(); 523 return selector; 524 } 525 526 public List<DragLine> getSelectors() { 527 return new ArrayList<DragLine>(selectors.values()); 528 } 529 530 /** 531 * @return Whether or not the channel selector is being displayed. 532 */ 533 public boolean displayingChannel() { 534 return (getSelector(channelSelector) != null); 535 } 536 537 public void removeRef(final DataReference thing) throws VisADException, 538 RemoteException 539 { 540 if (display == null) 541 return; 542 543 synchronized (displayedThings) { 544 displayedThings.remove(thing); 545 colorMaps.remove(thing); 546 idToRef.remove(thing.getName()); 547 display.removeReference(thing); 548 } 549 } 550 551 public void addRef(final DataReference thing, final Color color) 552 throws VisADException, RemoteException 553 { 554 if (display == null) 555 return; 556 557 synchronized (displayedThings) { 558 ConstantMap[] colorMap = makeColorMap(color); 559 560 displayedThings.add(thing); 561 idToRef.put(thing.getName(), thing); 562 ConstantMap[] constMaps; 563 if (data.hasBandNames()) { 564 constMaps = new ConstantMap[colorMap.length+2]; 565 System.arraycopy(colorMap, 0, constMaps, 0, colorMap.length); 566 constMaps[colorMap.length] = new ConstantMap(1f, Display.PointMode); 567 constMaps[colorMap.length+1] = new ConstantMap(5f, Display.PointSize); 568 } else { 569 constMaps = colorMap; 570 } 571 colorMaps.put(thing, constMaps); 572 573 display.addReference(thing, constMaps); 574 } 575 } 576 577 public void updateRef(final DataReference thing, final Color color) 578 throws VisADException, RemoteException 579 { 580 ConstantMap[] colorMap = makeColorMap(color); 581 ConstantMap[] constMaps; 582 if (data.hasBandNames()) { 583 constMaps = new ConstantMap[colorMap.length+2]; 584 System.arraycopy(colorMap, 0, constMaps, 0, colorMap.length); 585 constMaps[colorMap.length] = new ConstantMap(1f, Display.PointMode); 586 constMaps[colorMap.length+1] = new ConstantMap(5f, Display.PointSize); 587 } else { 588 constMaps = colorMap; 589 } 590 colorMaps.put(thing, constMaps); 591 idToRef.put(thing.getName(), thing); 592 refreshDisplay(); 593 } 594 595 public void reorderDataRefsById(final List<String> dataRefIds) { 596 if (dataRefIds == null) 597 throw new NullPointerException(""); 598 599 synchronized (displayedThings) { 600 try { 601 displayedThings.clear(); 602 for (String refId : dataRefIds) { 603 DataReference ref = idToRef.get(refId); 604 ConstantMap[] color = colorMaps.get(ref); 605 display.removeReference(ref); 606 display.addReference(ref, color); 607 } 608 } catch (Exception e) { } 609 } 610 } 611 612 // TODO: needs work 613 public boolean setWaveNumber(final float val) { 614 if (data == null) 615 return false; 616 617 if (waveNumber == val) 618 return true; 619 620 try { 621 if (spectrum == null) { 622 spectrum = data.getSpectrum(new int[] { 1, 1 }); 623 } 624 625 Gridded1DSet domain = (Gridded1DSet)spectrum.getDomainSet(); 626 int[] idx = domain.valueToIndex(new float[][] { { val } }); 627 float[][] tmp = domain.indexToValue(idx); 628 float channel = tmp[0][0]; 629 630 setSelectorValue(channelSelector, channel); 631 632 imageExpired = true; 633 } catch (Exception e) { 634 LogUtil.logException("MultiSpectralDisplay.setDisplayedWaveNum", e); 635 return false; 636 } 637 638 waveNumber = val; 639 640 if (data.hasBandNames()) { 641 String name = data.getBandNameFromWaveNumber(waveNumber); 642 bandSelectComboBox.setSelectedItem(name); 643 } 644 645 return true; 646 } 647 648 /** 649 * @return The ConstantMap representation of {@code color}. 650 */ 651 public static ConstantMap[] makeColorMap(final Color color) 652 throws VisADException, RemoteException 653 { 654 float r = color.getRed() / 255f; 655 float g = color.getGreen() / 255f; 656 float b = color.getBlue() / 255f; 657 float a = color.getAlpha() / 255f; 658 return new ConstantMap[] { new ConstantMap(r, Display.Red), 659 new ConstantMap(g, Display.Green), 660 new ConstantMap(b, Display.Blue), 661 new ConstantMap(a, Display.Alpha) }; 662 } 663 664 /** 665 * Provides {@code master} some sensible default attributes. 666 */ 667 private static void setDisplayMasterAttributes(final XYDisplay master) 668 throws VisADException, RemoteException 669 { 670 master.showAxisScales(true); 671 master.setAspect(2.5, 0.75); 672 673 double[] proj = master.getProjectionMatrix(); 674 proj[0] = 0.35; 675 proj[5] = 0.35; 676 proj[10] = 0.35; 677 678 master.setProjectionMatrix(proj); 679 } 680 681 /** 682 * @return The minimum and maximum values found on the x-axis. 683 */ 684 private static float[] getXRange(final Gridded1DSet domain) { 685 return new float[] { domain.getLow()[0], domain.getHi()[0] }; 686 } 687 688 public static RealType getRangeType(final FlatField spectrum) { 689 return (((FunctionType)spectrum.getType()).getFlatRange().getRealComponents())[0]; 690 } 691 692 private static RealType getDomainType(final FlatField spectrum) { 693 return (((FunctionType)spectrum.getType()).getDomain().getRealComponents())[0]; 694 } 695 696 private static class RubberBandBox extends CellImpl { 697 698 private static final String RBB = "_rubberband"; 699 700 private DataReference rubberBand; 701 702 private boolean init = false; 703 704 private ScalarMap xmap; 705 706 private ScalarMap ymap; 707 708 public RubberBandBox(final MultiSpectralDisplay msd, 709 final ScalarMap x, final ScalarMap y) throws VisADException, 710 RemoteException 711 { 712 RealType domainType = msd.getDomainType(); 713 RealType rangeType = msd.getRangeType(); 714 715 LocalDisplay display = msd.getDisplay(); 716 717 rubberBand = new DataReferenceImpl(hashCode() + RBB); 718 rubberBand.setData(new RealTuple(new RealTupleType(domainType, 719 rangeType), new double[] { Double.NaN, Double.NaN })); 720 721 display.addReferences(new RubberBandBoxRendererJ3D(domainType, 722 rangeType, 1, 1), new DataReference[] { rubberBand }, null); 723 724 xmap = x; 725 ymap = y; 726 727 this.addReference(rubberBand); 728 } 729 730 public void doAction() throws VisADException, RemoteException { 731 if (!init) { 732 init = true; 733 return; 734 } 735 736 Gridded2DSet set = (Gridded2DSet)rubberBand.getData(); 737 738 float[] low = set.getLow(); 739 float[] high = set.getHi(); 740 741 xmap.setRange(low[0], high[0]); 742 ymap.setRange(low[1], high[1]); 743 } 744 } 745 746 public static class DragLine extends CellImpl { 747 private final String selectorId = hashCode() + "_selector"; 748 private final String lineId = hashCode() + "_line"; 749 private final String controlId; 750 751 private ConstantMap[] mappings = new ConstantMap[5]; 752 753 private DataReference line; 754 755 private DataReference selector; 756 757 private MultiSpectralDisplay multiSpectralDisplay; 758 759 private HydraControl hydraControl; 760 761 private RealType domainType; 762 private RealType rangeType; 763 764 private RealTupleType tupleType; 765 766 private LocalDisplay display; 767 768 private float[] YRANGE; 769 770 private float lastSelectedValue; 771 772 public DragLine(final MultiSpectralDisplay msd, final String controlId, final Color color) throws Exception { 773 this(msd, controlId, makeColorMap(color)); 774 } 775 776 public DragLine(final MultiSpectralDisplay msd, final String controlId, final Color color, float[] YRANGE) throws Exception { 777 this(msd, controlId, makeColorMap(color), YRANGE); 778 } 779 780 public DragLine(final MultiSpectralDisplay msd, final String controlId, 781 final ConstantMap[] color) throws Exception 782 { 783 this(msd, controlId, color, new float[] {180f, 320f}); 784 } 785 786 public DragLine(final MultiSpectralDisplay msd, final String controlId, 787 final ConstantMap[] color, float[] YRANGE) throws Exception 788 { 789 if (msd == null) 790 throw new NullPointerException("must provide a non-null MultiSpectralDisplay"); 791 if (controlId == null) 792 throw new NullPointerException("must provide a non-null control ID"); 793 if (color == null) 794 throw new NullPointerException("must provide a non-null color"); 795 796 this.controlId = controlId; 797 this.multiSpectralDisplay = msd; 798 this.YRANGE = YRANGE; 799 lastSelectedValue = multiSpectralDisplay.getWaveNumber(); 800 801 for (int i = 0; i < color.length; i++) { 802 mappings[i] = (ConstantMap)color[i].clone(); 803 } 804 mappings[4] = new ConstantMap(-0.5, Display.YAxis); 805 806 Gridded1DSet domain = multiSpectralDisplay.getDomainSet(); 807 808 domainType = multiSpectralDisplay.getDomainType(); 809 rangeType = multiSpectralDisplay.getRangeType(); 810 tupleType = new RealTupleType(domainType, rangeType); 811 812 selector = new DataReferenceImpl(selectorId); 813 line = new DataReferenceImpl(lineId); 814 815 display = multiSpectralDisplay.getDisplay(); 816 817 display.addReferences(new GrabLineRendererJ3D(domain), new DataReference[] { selector }, new ConstantMap[][] { mappings }); 818 display.addReference(line, cloneMappedColor(color)); 819 820 addReference(selector); 821 } 822 823 private static ConstantMap[] cloneMappedColor(final ConstantMap[] color) throws Exception { 824 assert color != null && color.length >= 3 : color; 825 return new ConstantMap[] { 826 (ConstantMap)color[0].clone(), 827 (ConstantMap)color[1].clone(), 828 (ConstantMap)color[2].clone(), 829 }; 830 } 831 832 public void annihilate() { 833 try { 834 display.removeReference(selector); 835 display.removeReference(line); 836 } catch (Exception e) { 837 LogUtil.logException("DragLine.annihilate", e); 838 } 839 } 840 841 public String getControlId() { 842 return controlId; 843 } 844 845 /** 846 * Handles drag and drop updates. 847 */ 848 public void doAction() throws VisADException, RemoteException { 849 setSelectedValue(getSelectedValue()); 850 } 851 852 public float getSelectedValue() { 853 float val = (float)display.getDisplayRenderer().getDirectAxisValue(domainType); 854 if (Float.isNaN(val)) 855 val = lastSelectedValue; 856 return val; 857 } 858 859 public void setSelectedValue(final float val) throws VisADException, 860 RemoteException 861 { 862 // don't do work for stupid values 863 if ((Float.isNaN(val)) 864 || (selector.getThing() != null && val == lastSelectedValue)) 865 return; 866 867 line.setData(new Gridded2DSet(tupleType, 868 new float[][] { { val, val }, { YRANGE[0], YRANGE[1] } }, 2)); 869 870 selector.setData(new Real(domainType, val)); 871 lastSelectedValue = val; 872 873 if (hydraControl instanceof MultiSpectralControl) { 874 ((MultiSpectralControl) hydraControl).setWavelengthLabel 875 ( 876 MultiSpectralControl.WAVENUMLABEL + val 877 ); 878 } 879 multiSpectralDisplay.updateControlSelector(controlId, val); 880 } 881 882 /** 883 * Set the display control so we can call back and update 884 * wavelength readout in real time. 885 * 886 * @param hydraControl the display control to set 887 */ 888 889 public void setHydraControl(HydraControl hydraControl) { 890 this.hydraControl = hydraControl; 891 } 892 } 893}