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 */ 028package edu.wisc.ssec.mcidasv.probes; 029 030import static java.util.Objects.requireNonNull; 031import static visad.RealTupleType.SpatialCartesian2DTuple; 032import static visad.RealTupleType.SpatialEarth2DTuple; 033 034import java.awt.Color; 035import java.beans.PropertyChangeEvent; 036import java.beans.PropertyChangeListener; 037import java.rmi.RemoteException; 038import java.text.DecimalFormat; 039import java.util.List; 040import java.util.concurrent.CopyOnWriteArrayList; 041 042import ucar.unidata.collab.SharableImpl; 043import ucar.unidata.idv.control.DisplayControlImpl; 044import ucar.unidata.util.LogUtil; 045import ucar.unidata.view.geoloc.NavigatedDisplay; 046import ucar.visad.display.DisplayMaster; 047import ucar.visad.display.PointProbe; 048import ucar.visad.display.SelectorDisplayable; 049import ucar.visad.display.SelectorPoint; 050import ucar.visad.display.TextDisplayable; 051 052import visad.ActionImpl; 053import visad.ConstantMap; 054import visad.Data; 055import visad.Display; 056import visad.DisplayEvent; 057import visad.DisplayListener; 058import visad.FlatField; 059import visad.MathType; 060import visad.Real; 061import visad.RealTuple; 062import visad.RealTupleType; 063import visad.Text; 064import visad.TextType; 065import visad.Tuple; 066import visad.TupleType; 067import visad.VisADException; 068import visad.VisADGeometryArray; 069import visad.georef.EarthLocationTuple; 070import visad.georef.MapProjection; 071 072import edu.wisc.ssec.mcidasv.control.LambertAEA; 073import edu.wisc.ssec.mcidasv.util.MakeToString; 074import org.slf4j.Logger; 075import org.slf4j.LoggerFactory; 076 077/** 078 * {@code ReadoutProbe} is a probe that combines a {@literal "pickable"} probe 079 * widget with an adjacent text {@literal "readout"} of the data value at the 080 * probe's current location. 081 * 082 * <p>Primarily used with 083 * {@link edu.wisc.ssec.mcidasv.control.MultiSpectralControl}.</p> 084 */ 085public class ReadoutProbe 086 extends SharableImpl 087 implements PropertyChangeListener, DisplayListener 088{ 089 090 public static final String SHARE_PROFILE = 091 "ReadoutProbeDeux.SHARE_PROFILE"; 092 093 public static final String SHARE_POSITION = 094 "ReadoutProbeDeux.SHARE_POSITION"; 095 096 private static final Color DEFAULT_COLOR = Color.MAGENTA; 097 098 private static final TupleType TUPTYPE = makeTupleType(); 099 100 private static final Logger logger = 101 LoggerFactory.getLogger(ReadoutProbe.class); 102 103 private final List<ProbeListener> listeners = new CopyOnWriteArrayList<>(); 104 105 /** Displays the value of the data at the current position. */ 106 private final TextDisplayable valueDisplay = 107 createValueDisplay(DEFAULT_COLOR); 108 109 private final PointSelector pointSelector = 110 new PointSelector(getInitialProbePosition()); 111 112 private final DisplayMaster master; 113 114 private Color currentColor; 115 116 private String currentValue = "NaN"; 117 118 private double currentLatitude = Double.NaN; 119 private double currentLongitude = Double.NaN; 120 121 private float pointSize = 1.0f; 122 123 private FlatField field; 124 125 private static final DecimalFormat numFmt = new DecimalFormat(); 126 127 /** Used to keep track of the last zoom {@literal "level"}. */ 128 private float lastScale = Float.MIN_VALUE; 129 130 /** 131 * Create a {@literal "HYDRA"} probe that allows for displaying things 132 * like value at current position, current color, and location. 133 * 134 * <p>Note: <b>none</b> of the parameters permit {@code null} values.</p> 135 * 136 * @param control {@literal "Layer"} that will be probed. 137 * @param flatField Data to probe. 138 * @param color {@code Color} of the probe. 139 * @param pattern Format string to use with probe's location values. 140 * @param visible Whether or not the probe is visible. 141 * 142 * @throws NullPointerException if any of the given parameters are 143 * {@code null}. 144 * @throws VisADException if VisAD had problems. 145 * @throws RemoteException if VisAD had problems. 146 */ 147 public ReadoutProbe(final DisplayControlImpl control, 148 final FlatField flatField, 149 final Color color, 150 final String pattern, 151 final boolean visible) 152 throws VisADException, RemoteException 153 { 154 requireNonNull(control, "DisplayControlImpl can't be null"); 155 requireNonNull(flatField, "Field can't be null"); 156 requireNonNull(color, "Color can't be null"); 157 requireNonNull(pattern, "Pattern can't be null"); 158 159 master = control.getNavigatedDisplay(); 160 field = flatField; 161 162 initSharable(); 163 164 pointSelector.setColor(color); 165 valueDisplay.setVisible(visible); 166 valueDisplay.setColor(color); 167 currentColor = color; 168 pointSelector.setVisible(visible); 169 pointSelector.setPointSize(pointSize); 170 pointSelector.setAutoSize(true); 171 pointSelector.setPointSize(getDisplayScale()); 172 pointSelector.setZ(control.getZPosition()); 173 174 numFmt.applyPattern(pattern); 175 176 master.addDisplayable(valueDisplay); 177 master.addDisplayable(pointSelector); 178 setField(flatField); 179 180 // done mostly to avoid using "this" while we're still within the 181 // constructor 182 addListeners(); 183 } 184 185 /** 186 * Add this probe instance to the relevant listeners. 187 */ 188 private void addListeners() { 189 pointSelector.addPropertyChangeListener(this); 190 master.getDisplay().addDisplayListener(this); 191 } 192 193 /** 194 * Called whenever the probe fires off a {@link PropertyChangeEvent}. 195 * 196 * <p>Only handles position changes right now, all other events are 197 * discarded.</p> 198 * 199 * @param e Object that describes the property change. 200 * 201 * @throws NullPointerException if passed a {@code null} 202 * {@code PropertyChangeEvent}. 203 */ 204 @Override public void propertyChange(final PropertyChangeEvent e) { 205 requireNonNull(e, "Cannot handle a null property change event"); 206 if (e.getPropertyName().equals(SelectorDisplayable.PROPERTY_POSITION)) { 207 RealTuple prev = getEarthPosition(); 208 RealTuple current = getEarthPosition(); 209 fireProbePositionChanged(prev, current); 210 handleProbeUpdate(); 211 } 212 } 213 214 /** 215 * Called for events happening in the {@link visad.DisplayImpl} 216 * associated with {@link DisplayMaster}. 217 * 218 * <p>The only event that is actually handled is 219 * {@link DisplayEvent#FRAME_DONE}, which allows us to snap the text 220 * value displayable to the actual {@literal "pickable"} probe.</p> 221 * 222 * @param e Event to handle. 223 */ 224 @Override public void displayChanged(DisplayEvent e) { 225 // "snap" the text to the probe when zooming. the test for display 226 // scale values is to ensure we don't attempt to update if the zoom 227 // level didn't change. 228 if (e.getId() == DisplayEvent.FRAME_DONE) { 229 float currentScale = getDisplayScale(); 230 if (Float.compare(lastScale, currentScale) != 0) { 231 handleProbeUpdate(); 232 lastScale = currentScale; 233 } 234 } 235 } 236 237 /** 238 * Sets the {@link FlatField} associated with this probe to the given 239 * {@code field}. 240 * 241 * @param flatField New {@code FlatField} for this probe. 242 * 243 * @throws NullPointerException if passed a {@code null} {@code field}. 244 */ 245 public void setField(final FlatField flatField) { 246 requireNonNull(flatField); 247 this.field = flatField; 248 handleProbeUpdate(); 249 } 250 251 /** 252 * Adds a {@link ProbeListener} to the listener list so that it can be 253 * notified when the probe is changed. 254 * 255 * @param listener {@code ProbeListener} to register. {@code null} 256 * listeners are not allowed. 257 * 258 * @throws NullPointerException if {@code listener} is null. 259 */ 260 public void addProbeListener(final ProbeListener listener) { 261 requireNonNull(listener, "Can't add a null listener"); 262 listeners.add(listener); 263 } 264 265 /** 266 * Removes a {@link ProbeListener} from the notification list. 267 * 268 * @param listener {@code ProbeListener} to remove. {@code null} values 269 * are permitted, but since they are not allowed to be added... 270 */ 271 public void removeProbeListener(final ProbeListener listener) { 272 listeners.remove(listener); 273 } 274 275 /** 276 * Determine whether or not a given {@link ProbeListener} is listening to 277 * the current probe. 278 * 279 * @param listener {@code ProbeListener} to check. {@code null} values are 280 * permitted. 281 * 282 * @return {@code true} if {@code listener} has been added to the list of 283 * {@code ProbeListener} objects, {@code false} otherwise. 284 */ 285 public boolean hasListener(final ProbeListener listener) { 286 return listeners.contains(listener); 287 } 288 289 /** 290 * Notifies the registered {@link ProbeListener ProbeListeners} that this 291 * probe's position has changed. 292 * 293 * @param previous Previous position. Cannot be {@code null}. 294 * @param current Current position. Cannot be {@code null}. 295 */ 296 protected void fireProbePositionChanged(final RealTuple previous, 297 final RealTuple current) 298 { 299 requireNonNull(previous); 300 requireNonNull(current); 301 302 ProbeEvent<RealTuple> event = 303 new ProbeEvent<>(this, previous, current); 304 for (ProbeListener listener : listeners) { 305 listener.probePositionChanged(event); 306 } 307 } 308 309 /** 310 * Notifies the registered {@link ProbeListener ProbeListeners} that this 311 * probe's color has changed. 312 * 313 * @param previous Previous color. Cannot be {@code null}. 314 * @param current Current color. Cannot be {@code null}. 315 */ 316 protected void fireProbeColorChanged(final Color previous, 317 final Color current) 318 { 319 requireNonNull(previous); 320 requireNonNull(current); 321 322 ProbeEvent<Color> event = 323 new ProbeEvent<>(this, previous, current); 324 for (ProbeListener listener : listeners) { 325 listener.probeColorChanged(event); 326 } 327 } 328 329 /** 330 * Notifies registered {@link ProbeListener ProbeListeners} that this 331 * probe's visibility has changed. Only takes a {@literal "previous"} 332 * value, which is negated to form the {@literal "current"} value. 333 * 334 * @param previous Visibility <b>before</b> change. 335 */ 336 protected void fireProbeVisibilityChanged(final boolean previous) { 337 ProbeEvent<Boolean> event = 338 new ProbeEvent<>(this, previous, !previous); 339 for (ProbeListener listener : listeners) { 340 listener.probeVisibilityChanged(event); 341 } 342 } 343 344 /** 345 * Notifies the registered {@link ProbeListener ProbeListeners} that this 346 * probe's location format pattern has changed. 347 * 348 * @param previous Previous location format pattern. 349 * @param current Current location format pattern. 350 */ 351 protected void fireProbeFormatPatternChanged(final String previous, 352 final String current) 353 { 354 ProbeEvent<String> event = 355 new ProbeEvent<>(this, previous, current); 356 for (ProbeListener listener : listeners) { 357 listener.probeFormatPatternChanged(event); 358 } 359 } 360 361 /** 362 * Change the color of this {@code ReadoutProbe} instance. 363 * 364 * @param color New color. Cannot be {@code null}. 365 */ 366 public void setColor(final Color color) { 367 requireNonNull(color, "Cannot set a probe to a null color"); 368 setColor(color, false); 369 } 370 371 public PointSelector getPointSelector() { 372 return pointSelector; 373 } 374 375 public TextDisplayable getValueDisplay() { 376 return valueDisplay; 377 } 378 379 /** 380 * Change the color of this {@code ReadoutProbe} instance and control 381 * whether or not listeners should be notified. 382 * 383 * <p>Note that if {@code color} is the same as {@code currentColor}, 384 * nothing will happen (the method exits early).</p> 385 * 386 * @param color New color for this probe. Cannot be {@code null}. 387 * @param quietly Whether or not to notify the list of 388 * {@link ProbeListener ProbeListeners} of a color change. 389 */ 390 private void setColor(final Color color, final boolean quietly) { 391 assert color != null; 392 393 if (currentColor.equals(color)) { 394 return; 395 } 396 397 try { 398 pointSelector.setColor(color); 399 valueDisplay.setColor(color); 400 Color prev = currentColor; 401 currentColor = color; 402 403 if (!quietly) { 404 fireProbeColorChanged(prev, currentColor); 405 } 406 } catch (Exception e) { 407 LogUtil.logException("Couldn't set the color of the probe", e); 408 } 409 } 410 411 /** 412 * Get the current color of this {@code ReadoutProbe} instance. 413 * 414 * @return {@code Color} of this {@code ReadoutProbe}. 415 */ 416 public Color getColor() { 417 return currentColor; 418 } 419 420 /** 421 * Get the current {@literal "readout value"} of this 422 * {@code ReadoutProbe} instance. 423 * 424 * @return The value of the data at the probe's current location. 425 */ 426 public String getValue() { 427 return currentValue; 428 } 429 430 /** 431 * Get the current latitude of this {@code ReadoutProbe} instance. 432 * 433 * @return Current latitude of the probe. 434 */ 435 public double getLatitude() { 436 return currentLatitude; 437 } 438 439 /** 440 * Get the current longitude of this {@code ReadoutProbe} instance. 441 * 442 * @return Current longitude of the probe. 443 */ 444 public double getLongitude() { 445 return currentLongitude; 446 } 447 448 public void setLatLon(final double latitude, final double longitude) { 449 try { 450 EarthLocationTuple elt = 451 new EarthLocationTuple(latitude, longitude, 0.0); 452 double[] tmp = 453 ((NavigatedDisplay)master).getSpatialCoordinates(elt, null); 454 pointSelector.setPosition(tmp[0], tmp[1]); 455 } catch (Exception e) { 456 LogUtil.logException("Failed to set the pointSelector's position", e); 457 } 458 } 459 460 public void quietlySetVisible(final boolean visibility) { 461 try { 462 pointSelector.setVisible(visibility); 463 valueDisplay.setVisible(visibility); 464 } catch (Exception e) { 465 LogUtil.logException("Couldn't set the probe's internal visibility", e); 466 } 467 } 468 469 public void quietlySetColor(final Color newColor) { 470 setColor(newColor, true); 471 } 472 473 /** 474 * Update the location format pattern for the current probe. 475 * 476 * @param pattern New location format pattern. Cannot be {@code null}. 477 */ 478 public void setFormatPattern(final String pattern) { 479 setFormatPattern(pattern, false); 480 } 481 482 /** 483 * Update the location format pattern for the current probe, but 484 * <b>do not</b> fire off any events. 485 * 486 * @param pattern New location format pattern. Cannot be {@code null}. 487 */ 488 public void quietlySetFormatPattern(final String pattern) { 489 setFormatPattern(pattern, true); 490 } 491 492 /** 493 * Update the location format pattern for the current probe and optionally 494 * fire off an update event. 495 * 496 * @param pattern New location format pattern. Cannot be {@code null}. 497 * @param quietly Whether or not to fire a format pattern change update. 498 */ 499 private void setFormatPattern(final String pattern, 500 final boolean quietly) 501 { 502 String previous = numFmt.toPattern(); 503 numFmt.applyPattern(pattern); 504 if (!quietly) { 505 fireProbeFormatPatternChanged(previous, pattern); 506 } 507 } 508 509 /** 510 * Returns the number format string current being used. 511 * 512 * @return Location format pattern string. 513 */ 514 public String getFormatPattern() { 515 return numFmt.toPattern(); 516 } 517 518 public void handleProbeUpdate() { 519 RealTuple pos = getEarthPosition(); 520 if (pos == null) { 521 return; 522 } 523 524 Tuple positionValue = valueAtPosition(pos, field); 525 if (positionValue == null) { 526 return; 527 } 528 529 try { 530 valueDisplay.setData(positionValue); 531 } catch (Exception e) { 532 LogUtil.logException("Failed to set readout value", e); 533 } 534 } 535 536 /** 537 * Called when this probe has been removed. 538 */ 539 public void handleProbeRemoval() { 540 listeners.clear(); 541 try { 542 master.getDisplay().removeDisplayListener(this); 543 master.removeDisplayable(valueDisplay); 544 master.removeDisplayable(pointSelector); 545 } catch (Exception e) { 546 LogUtil.logException("Problem removing visible portions of readout probe", e); 547 } 548 currentColor = null; 549 field = null; 550 } 551 552 /** 553 * Get the scaling factor for probes and such. The scaling is 554 * the parameter that gets passed to TextControl.setSize() and 555 * ShapeControl.setScale(). 556 * 557 * @return ratio of the current matrix scale factor to the 558 * saved matrix scale factor. 559 */ 560 public final float getDisplayScale() { 561 float scale = 1.0f; 562 try { 563 scale = master.getDisplayScale(); 564 } catch (Exception e) { 565 LogUtil.logException("Error getting display scale.", e); 566 } 567 return scale; 568 } 569 570 public void setXYPosition(final RealTuple position) { 571 if (position == null) { 572 throw new NullPointerException("cannot use a null position"); 573 } 574 575 try { 576 pointSelector.setPosition(position); 577 } catch (Exception e) { 578 LogUtil.logException("Had problems setting probe's xy position", e); 579 } 580 } 581 582 public RealTuple getXYPosition() { 583 RealTuple position = null; 584 try { 585 position = pointSelector.getPosition(); 586 } catch (Exception e) { 587 LogUtil.logException("Could not determine the probe's xy location", e); 588 } 589 return position; 590 } 591 592 /** 593 * Get the current {@literal "earth location"} of the probe. 594 * 595 * <p>Note: this method will attempt to change the {@link #currentLatitude} 596 * and {@link #currentLongitude} fields.</p> 597 * 598 * @return Location of {@link #pointSelector}, or {@code null} if the 599 * location could not be determined. 600 */ 601 public EarthLocationTuple getEarthPosition() { 602 EarthLocationTuple earthTuple = null; 603 try { 604 double[] values = pointSelector.getPosition().getValues(); 605 earthTuple = (EarthLocationTuple)((NavigatedDisplay)master).getEarthLocation(values[0], values[1], 1.0, true); 606 currentLatitude = earthTuple.getLatitude().getValue(); 607 currentLongitude = earthTuple.getLongitude().getValue(); 608 } catch (Exception e) { 609 LogUtil.logException("Could not determine the probe's earth location", e); 610 } 611 return earthTuple; 612 } 613 614 /** 615 * Respond to the projection having been changed. 616 * 617 * @param newProjection New projection. Can be {@code null}. 618 */ 619 public void projectionChanged(MapProjection newProjection) { 620 setLatLon(currentLatitude, currentLongitude); 621 handleProbeUpdate(); 622 } 623 624 private Tuple valueAtPosition(final RealTuple position, 625 final FlatField imageData) 626 { 627 assert position != null : "Cannot provide a null position"; 628 assert imageData != null : "Cannot provide a null image"; 629 double[] values = position.getValues(); 630 631 // offset slightly so that the value readout isn't directly on top of 632 // the actual pointSelector 633 double offset = 0.5 * getDisplayScale(); 634 635 if (values[1] < -180) { 636 values[1] += 360f; 637 } 638 639 if (values[0] > 180) { 640 values[0] -= 360f; 641 } 642 643 Tuple positionTuple = null; 644 try { 645 // TODO(jon): do the positionFormat stuff in here. maybe this'll 646 // have to be an instance method? 647 648 // "corrected" is where the text should be positioned 649 RealTuple corrected = makeEarth2dTuple(values[0] + offset, 650 values[1] + offset); 651 652 // "probeLoc" is where pointSelector is positioned 653 RealTuple probeLoc = makeEarth2dTuple(values[0], values[1]); 654 655 Real realVal = (Real)imageData.evaluate(probeLoc, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS); 656 float val = (float)realVal.getValue(); 657 if (Float.isNaN(val)) { 658 currentValue = "NaN"; 659 } else { 660 currentValue = numFmt.format(realVal.getValue()); 661 } 662 positionTuple = new Tuple(TUPTYPE, new Data[] { corrected, new Text(TextType.Generic, currentValue) }); 663 } catch (Exception e) { 664 LogUtil.logException("Encountered trouble when determining value at pointSelector position", e); 665 } 666 return positionTuple; 667 } 668 669 /** 670 * Returns a {@link RealTupleType#SpatialEarth2DTuple SpatialEarth2DTuple} 671 * for the given latitude and longitude. 672 * 673 * <p>Be aware that for whatever reason VisAD wants the longitude first, 674 * then the latitude.</p> 675 * 676 * @param lat Latitude of the position. 677 * @param lon Longitude of the position. 678 * 679 * @return {@code SpatialEarth2DTuple} containing {@code lat} and 680 * {@code lon}. 681 * 682 * @throws VisADException Problem creating VisAD object. 683 * @throws RemoteException Java RMI error. 684 */ 685 public static RealTuple makeEarth2dTuple(double lat, double lon) 686 throws VisADException, RemoteException 687 { 688 return new RealTuple(SpatialEarth2DTuple, new double[] { lon, lat }); 689 } 690 691 private static RealTuple getInitialProbePosition() { 692 RealTuple position = null; 693 try { 694 position = new RealTuple(SpatialCartesian2DTuple, 695 new double[] { 0.0, 0.0 }); 696 } catch (Exception e) { 697 LogUtil.logException("Problem with finding an initial probe position", e); 698 } 699 return position; 700 } 701 702 private static TextDisplayable createValueDisplay(final Color color) { 703 assert color != null; 704 705 DecimalFormat fmt = new DecimalFormat(); 706 fmt.setMaximumIntegerDigits(3); 707 fmt.setMaximumFractionDigits(1); 708 709 TextDisplayable td = null; 710 try { 711 td = new TextDisplayable(TextType.Generic); 712 td.setLineWidth(2f); 713 td.setColor(color); 714 td.setNumberFormat(fmt); 715 } catch (Exception e) { 716 LogUtil.logException("Problem creating readout value container", e); 717 } 718 return td; 719 } 720 721 private static TupleType makeTupleType() { 722 TupleType t = null; 723 try { 724 t = new TupleType(new MathType[] { SpatialEarth2DTuple, TextType.Generic }); 725 } catch (Exception e) { 726 LogUtil.logException("Problem creating readout tuple type", e); 727 } 728 return t; 729 } 730 731 /** 732 * Returns a brief summary of a ReadoutProbe. Please note that this format 733 * is subject to change. 734 * 735 * @return String that looks like {@code [ReadProbe@HASHCODE: color=..., 736 * latitude=..., longitude=..., value=...]} 737 */ 738 public String toString() { 739 return MakeToString.fromInstance(this) 740 .add("color", currentColor) 741 .add("latitude", currentLatitude) 742 .add("longitude", currentLongitude) 743 .add("value", currentValue).toString(); 744 } 745 746 /** 747 * This class is a reimplementation of {@link PointProbe} that whose 748 * mouse movement is limited to the x- and y- axes. 749 * 750 * <p>To change the position of the instance along the z-axis, try something 751 * like the following: 752 * {@code new PointSelector().setZ(zPosition)}. 753 * </p> 754 */ 755 public static class PointSelector extends SelectorDisplayable { 756 757 /** pointSelector */ 758 private SelectorPoint point; 759 760 /** flag for whether we're in the process of setting the position */ 761 private volatile boolean settingPosition = false; 762 763 /** 764 * Construct a point pointSelector. 765 * 766 * @throws VisADException Problem creating VisAD object. 767 * @throws RemoteException Java RMI error. 768 */ 769 public PointSelector() throws VisADException, RemoteException { 770 this(0, 0); 771 } 772 773 /** 774 * Construct a point pointSelector at the location specified. 775 * 776 * @param x X position. 777 * @param y Y position. 778 * 779 * @throws VisADException Problem creating VisAD object. 780 * @throws RemoteException Java RMI error. 781 */ 782 public PointSelector(double x, double y) 783 throws VisADException, RemoteException 784 { 785 this(new RealTuple(SpatialCartesian2DTuple, 786 new double[] { x, y })); 787 } 788 789 /** 790 * Construct a pointSelector at the position specified. 791 * 792 * @param position Position of the pointSelector. 793 * 794 * @throws VisADException Problem creating VisAD object. 795 * @throws RemoteException Java RMI error. 796 */ 797 public PointSelector(RealTuple position) 798 throws VisADException, RemoteException 799 { 800 point = new SelectorPoint("Probe point", position); 801 802 addDisplayable(point); 803 setPosition(position); 804 point.addAction(new ActionImpl("point listener") { 805 @Override public void doAction() { 806 if (settingPosition) { 807 return; 808 } 809 notifyListenersOfMove(); 810 } 811 }); 812 } 813 814 /** 815 * Get the selector point 816 * 817 * @return the selector point 818 */ 819 public SelectorPoint getSelectorPoint() { 820 return point; 821 } 822 823 /** 824 * Set if any of the axis movements are fixed 825 * 826 * @param x x fixed 827 * @param y y fixed 828 * @param z z fixed 829 */ 830 public void setFixed(boolean x, boolean y, boolean z) { 831 point.setFixed(x, y, z); 832 } 833 834 public void setZ(double newz) { 835 try { 836 point.addConstantMap(new ConstantMap(newz, Display.ZAxis)); 837 } catch (VisADException | RemoteException e) { 838 logger.error("problem setting z", e); 839 } 840 } 841 842 /** 843 * Get the point scale 844 * 845 * @return the point scale 846 */ 847 public float getPointScale() { 848 if (point != null) { 849 return point.getScale(); 850 } 851 return 1.0f; 852 } 853 854 /** 855 * Set the type of marker used for the pointSelector. 856 * 857 * @param marker marker as a VisADGeometryArray 858 * 859 * @throws RemoteException Java RMI error 860 * @throws VisADException Problem creating VisAD object. 861 */ 862 public void setMarker(VisADGeometryArray marker) 863 throws VisADException, RemoteException 864 { 865 point.setMarker(marker); 866 } 867 868 /** 869 * Set the type of marker used for the pointSelector. 870 * 871 * @param marker {@link ucar.visad.ShapeUtility ShapeUtility} marker. 872 * 873 * @throws VisADException Problem creating VisAD object. 874 * @throws RemoteException Java RMI error. 875 */ 876 public void setMarker(String marker) 877 throws VisADException, RemoteException 878 { 879 point.setMarker(marker); 880 } 881 882 /** 883 * Set whether the marker should automatically resize as the 884 * display is zoomed. 885 * 886 * @param yesorno true to automatically resize the marker. 887 * 888 * @throws VisADException Problem creating VisAD object. 889 * @throws RemoteException Java RMI error. 890 */ 891 public void setAutoSize(boolean yesorno) 892 throws VisADException, RemoteException 893 { 894 point.setAutoSize(yesorno); 895 } 896 897 /** 898 * Get the position of the pointSelector. 899 * 900 * @return Current position. 901 */ 902 public RealTuple getPosition() { 903 return point.getPoint(); 904 } 905 906 /** 907 * Set the pointSelector's x/y position 908 * 909 * @param x X position. 910 * @param y X position. 911 * 912 * @throws VisADException Problem creating VisAD object. 913 * @throws RemoteException Java RMI error. 914 */ 915 public void setPosition(double x, double y) 916 throws VisADException, RemoteException 917 { 918 setPosition( 919 new RealTuple(SpatialCartesian2DTuple, 920 new double[] { x, y })); 921 } 922 923 /** 924 * Set the pointSelector's position. 925 * 926 * @param position Position of the pointSelector 927 * 928 * @throws VisADException Problem creating VisAD object. 929 * @throws RemoteException Java RMI error. 930 */ 931 public void setPosition(RealTuple position) 932 throws VisADException, RemoteException 933 { 934 settingPosition = true; 935 try { 936 point.setPoint(position); 937 } finally { 938 settingPosition = false; 939 } 940 } 941 } 942}