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.control; 030 031 032import java.awt.Color; 033import java.awt.event.ActionEvent; 034import java.awt.event.ActionListener; 035import java.beans.PropertyChangeEvent; 036import java.rmi.RemoteException; 037import java.text.DecimalFormat; 038import java.util.List; 039 040import javax.swing.JMenu; 041import javax.swing.JMenuItem; 042 043import visad.CoordinateSystem; 044import visad.Data; 045import visad.DataReference; 046import visad.DataReferenceImpl; 047import visad.DisplayEvent; 048import visad.DisplayListener; 049import visad.FlatField; 050import visad.FunctionType; 051import visad.MathType; 052import visad.Real; 053import visad.RealTuple; 054import visad.RealTupleType; 055import visad.RealType; 056import visad.Text; 057import visad.TextType; 058import visad.Tuple; 059import visad.TupleType; 060import visad.VisADException; 061import visad.georef.EarthLocationTuple; 062import visad.georef.LatLonPoint; 063 064import ucar.unidata.collab.Sharable; 065import ucar.unidata.data.grid.GridUtil; 066import ucar.unidata.idv.ViewDescriptor; 067import ucar.unidata.idv.control.GridDisplayControl; 068import ucar.unidata.util.GuiUtils; 069import ucar.unidata.util.LogUtil; 070import ucar.unidata.util.Misc; 071import ucar.unidata.util.TwoFacedObject; 072import ucar.unidata.view.geoloc.NavigatedDisplay; 073import ucar.visad.ShapeUtility; 074import ucar.visad.display.DisplayMaster; 075import ucar.visad.display.PointProbe; 076import ucar.visad.display.SelectorDisplayable; 077import ucar.visad.display.TextDisplayable; 078 079 080 081/** 082 * An abstract base class that manages a vertical probe 083 * To create a probe call doMakeProbe 084 * To be notified of changes override: 085 * void probePositionChanged (double x, double y); 086 * 087 * @author IDV development team 088 * @version $Revision$Date: 2011/03/24 16:06:32 $ 089 */ 090public class Grid2DReadoutProbe extends GridDisplayControl { 091 092 /** profile sharing property */ 093 public static final String SHARE_PROFILE = 094 "LineProbeControl.SHARE_PROFILE"; 095 096 /** the line probe */ 097 //-protected LineProbe probe; 098 protected PointProbe probe; 099 100 /** the initial position */ 101 private RealTuple initPosition; 102 103 /** The shape for the probe point */ 104 private String marker; 105 106 /** The point size */ 107 private float pointSize = 1.0f; 108 109 /** Keep around for the label macros */ 110 protected String positionText; 111 112 private static final TupleType TUPTYPE = makeTupleType(); 113 114 private DataReference positionRef = null; 115 116 private Color currentColor = Color.MAGENTA; 117 118 private RealTuple currentPosition = null; 119 120 private Tuple locationValue = null; 121 122 private TextDisplayable valueDisplay = null; 123 124 private FlatField image = null; 125 126 private RealTupleType earthTupleType = null; 127 128 private boolean isLonLat = true; 129 130 private DisplayMaster master; 131 132 private DecimalFormat numFmt; 133 134 /** 135 * Default Constructor. 136 */ 137 public Grid2DReadoutProbe(FlatField grid2d, DisplayMaster master) 138 throws VisADException, RemoteException { 139 super(); 140 earthTupleType = check2DEarthTuple(grid2d); 141 if (earthTupleType != null) { 142 isLonLat = earthTupleType.equals(RealTupleType.SpatialEarth2DTuple); 143 } 144 setAttributeFlags(FLAG_COLOR); 145 initSharable(); 146 147 currentPosition = new RealTuple(RealTupleType.Generic2D); 148 149 positionRef = new DataReferenceImpl(hashCode() + "_positionRef"); 150 151 valueDisplay = createValueDisplayer(currentColor); 152 this.image = grid2d; 153 this.master = master; 154 155 master.addDisplayable(valueDisplay); 156 setSharing(true); 157 158 master.getDisplay().addDisplayListener( new DisplayListener() { 159 public void displayChanged(DisplayEvent de) { 160 if (de.getId() == DisplayEvent.MOUSE_RELEASED) { 161 try { 162 RealTuple position = getPosition(); 163 doShare(SHARE_POSITION, position); 164 } catch (Exception e) { 165 logException("doMoveProfile", e); 166 } 167 } 168 } 169 }); 170 numFmt = new DecimalFormat(); 171 numFmt.setMaximumFractionDigits(2); 172 } 173 174 /** 175 * Default doMakeProbe method. 176 * 177 * @throws RemoteException Java RMI error 178 * @throws VisADException VisAD Error 179 */ 180 public void doMakeProbe() throws VisADException, RemoteException { 181 doMakeProbe(getColor()); 182 } 183 184 /** 185 * Make the probe with the specific {@code Color}. 186 * 187 * @param c color for probe. 188 * 189 * @throws RemoteException Java RMI error 190 * @throws VisADException VisAD Error 191 */ 192 public void doMakeProbe(Color c) throws VisADException, RemoteException { 193 //doMakeProbe(c, getDefaultViewDescriptor()); 194 } 195 196 197 /** 198 * Make the probe with the specific {@code ViewDescriptor}. 199 * 200 * @param view view descriptor 201 * 202 * @throws RemoteException Java RMI error 203 * @throws VisADException VisAD Error 204 */ 205 public void doMakeProbe(ViewDescriptor view) 206 throws VisADException, RemoteException { 207 //doMakeProbe(getColor(), view); 208 } 209 210 /** 211 * Make the probe with the specific {@link Color} and associate it with 212 * the given {@link DisplayMaster}. 213 * 214 * @param probeColor Color of the probe. 215 * @param master {@code DisplayMaster} of the display we will be probing. 216 * 217 * @throws RemoteException Java RMI error 218 * @throws VisADException VisAD Error 219 */ 220 //-public void doMakeProbe(Color probeColor, ViewDescriptor view) 221 public void doMakeProbe(Color probeColor, DisplayMaster master) 222 throws VisADException, RemoteException { 223 probe = null; 224 /* 225 if (getDisplayAltitudeType().equals(Display.Radius)) { 226 // System.err.println("Probe 1"); 227 probe = new LineProbe( 228 new RealTuple( 229 RealTupleType.SpatialEarth2DTuple, new double[] { 0, 230 0 })); 231 */ 232 if (initPosition != null) { 233 // System.err.println("Probe 2"); 234 //-probe = new LineProbe(initPosition); 235 probe = new PointProbe(initPosition); 236 } else { 237 // System.err.println("Probe 3"); 238 //-probe = new LineProbe(getInitialLinePosition()); 239 probe = new PointProbe(getInitialLinePosition()); 240 // probe = new LineProbe(getGridCenterPosition()); 241 } 242 initPosition = probe.getPosition(); 243 244 // it is a little colored cube 8 pixels across 245 probe.setColor(probeColor); 246 probe.setVisible(true); 247 probe.addPropertyChangeListener(this); 248 probe.setPointSize(1f); 249 if (marker != null) { 250 /*probe.setMarker( 251 SelectorPoint.reduce(ShapeUtility.makeShape(marker))); */ 252 } 253 probe.setAutoSize(true); 254 master.addDisplayable(probe); 255 } 256 257 258 /** 259 * Handle changes 260 * 261 * @param evt The event 262 */ 263 public void propertyChange(PropertyChangeEvent evt) { 264 if (evt.getPropertyName().equals( 265 SelectorDisplayable.PROPERTY_POSITION)) { 266 doMoveProbe(); 267 } else { 268 super.propertyChange(evt); 269 } 270 } 271 272 /** 273 * Reset the position of the probe to the center. 274 */ 275 public void resetProbePosition() { 276 try { 277 setProbePosition(0.0, 0.0); 278 } catch (Exception exc) { 279 logException("Resetting probe position", exc); 280 } 281 } 282 283 284 /** 285 * Get edit menu items 286 * 287 * @param items list of menu items 288 * @param forMenuBar true if for the menu bar 289 */ 290 protected void getEditMenuItems(List items, boolean forMenuBar) { 291 if (probe != null) { 292 JMenuItem mi = new JMenuItem("Reset Probe Position"); 293 mi.addActionListener(new ActionListener() { 294 public void actionPerformed(ActionEvent ae) { 295 resetProbePosition(); 296 } 297 }); 298 items.add(mi); 299 } 300 super.getEditMenuItems(items, forMenuBar); 301 } 302 303 /** 304 * Set the probe position. Probes are set in XY space. 305 * 306 * @param xy X and Y position of the probe. 307 * 308 * @throws VisADException problem setting probe position 309 * @throws RemoteException problem setting probe position on remote display 310 */ 311 public void setProbePosition(RealTuple xy) 312 throws VisADException, RemoteException { 313 probe.setPosition(xy); 314 } 315 316 /** 317 * Set the probe position from display x and y positions. 318 * 319 * @param x X position of the probe. 320 * @param y Y position of the probe. 321 * 322 * @throws VisADException problem setting probe position 323 * @throws RemoteException problem setting probe position on remote display 324 */ 325 public void setProbePosition(double x, double y) 326 throws VisADException, RemoteException { 327 setProbePosition(new RealTuple(new Real[] { 328 new Real(RealType.XAxis, x), 329 new Real(RealType.YAxis, y) })); 330 } 331 332 /** 333 * Set the initial position of the probe. This is used by the 334 * XML persistense. 335 * 336 * @param p position 337 */ 338 public void setPosition(RealTuple p) { 339 initPosition = p; 340 } 341 342 /** 343 * Get the position of the probe. This is used by the 344 * XML persistense. 345 * 346 * @return current probe position or null if probe has not been created. 347 * 348 * @throws RemoteException Java RMI error 349 * @throws VisADException VisAD Error 350 */ 351 public RealTuple getPosition() throws VisADException, RemoteException { 352 return ((probe != null) 353 ? probe.getPosition() 354 : null); 355 } 356 357 /** 358 * Get the initial position of the probe set during unpersistence. 359 * 360 * @return initial position or {@code null} if not set during 361 * initialization. 362 */ 363 public RealTuple getInitialPosition() { 364 return initPosition; 365 } 366 367 /** 368 * Method called when sharing is enabled. 369 * 370 * @param from Sharable that send the data. 371 * @param dataId identifier for data to be shared 372 * @param data data to be shared. 373 */ 374 public void receiveShareData(Sharable from, Object dataId, 375 Object[] data) { 376 if (dataId.equals(SHARE_POSITION)) { 377 if (probe == null) { 378 return; 379 } 380 try { 381 probe.setPosition((RealTuple) data[0]); 382 probePositionChanged(getPosition()); 383 } catch (Exception e) { 384 logException("receiveShareData:" + dataId, e); 385 } 386 return; 387 } 388 super.receiveShareData(from, dataId, data); 389 } 390 391 392 /** 393 * Method called when probe is moved. 394 */ 395 protected void doMoveProbe() { 396 try { 397 RealTuple position = getPosition(); 398 probePositionChanged(position); 399 //-doShare(SHARE_POSITION, position); 400 } catch (Exception e) { 401 logException("doMoveProfile", e); 402 } 403 } 404 405 /** 406 * This gets called when either the user moves the probe point or 407 * when we get a sharable event to move the probe point. Subclasses 408 * need to implement this. 409 * 410 * @param newPos New position for the probe. 411 */ 412 protected void probePositionChanged(final RealTuple newPos) { 413 if (!currentPosition.equals(newPos)) { 414 updatePosition(newPos); 415 updateLocationValue(); 416 currentPosition = newPos; 417 } 418 } 419 420 protected void updatePosition(final RealTuple position) { 421 double[] vals = position.getValues(); 422 try { 423 EarthLocationTuple elt = (EarthLocationTuple)boxToEarth( 424 new double[] { vals[0], vals[1], 1.0 }); 425 426 positionRef.setData(elt.getLatLonPoint()); 427 } catch (Exception e) { 428 LogUtil.logException("HydraImageProbe.updatePosition", e); 429 } 430 } 431 432 private void updateLocationValue() { 433 Tuple tup = null; 434 RealTuple earthTuple; 435 436 437 try { 438 RealTuple location = (RealTuple)positionRef.getData(); 439 440 if (location == null) { 441 return; 442 } 443 444 if (image == null) { 445 return; 446 } 447 448 double[] vals = location.getValues(); 449 if (vals[1] < -180) { 450 vals[1] += 360f; 451 } 452 453 if (vals[1] > 180) { 454 vals[1] -= 360f; 455 } 456 457 if (earthTupleType != null) { 458 RealTuple lonLat = 459 new RealTuple(RealTupleType.SpatialEarth2DTuple, 460 new double[] { vals[1], vals[0] }); 461 RealTuple latLon = new RealTuple(RealTupleType.LatitudeLongitudeTuple, 462 new double[] { vals[0], vals[1] }); 463 RealTuple rtup = lonLat; 464 if (!(isLonLat)) { 465 rtup = latLon; 466 } 467 468 Real val = null; 469 Data dat = image.evaluate(rtup, Data.NEAREST_NEIGHBOR, Data.NO_ERRORS); 470 471 if ( ((FunctionType)image.getType()).getRange() instanceof RealTupleType ) { 472 RealTuple tmp = (RealTuple)dat; 473 val = (tmp.getRealComponents())[0]; 474 } else { 475 val = (Real)dat; 476 } 477 float fval = (float)val.getValue(); 478 479 tup = new Tuple(TUPTYPE, 480 new Data[] { lonLat, new Text(TextType.Generic, numFmt.format(fval)) }); 481 } 482 483 valueDisplay.setData(tup); 484 } catch (Exception e) { 485 LogUtil.logException("HydraImageProbe.updateLocationValue", e); 486 } 487 488 if (tup != null) { 489 locationValue = tup; 490 } 491 } 492 493 public NavigatedDisplay getNavigatedDisplay() { 494 return (NavigatedDisplay)master; 495 } 496 497 public static RealTupleType check2DEarthTuple(FlatField field) { 498 CoordinateSystem cs; 499 FunctionType ftype = (FunctionType) field.getType(); 500 RealTupleType domain = ftype.getDomain(); 501 if ( (domain.equals(RealTupleType.SpatialEarth2DTuple)) || 502 (domain.equals(RealTupleType.LatitudeLongitudeTuple)) ) { 503 return domain; 504 } else if ((cs = domain.getCoordinateSystem()) != null) { 505 RealTupleType ref = cs.getReference(); 506 if (ref.equals(RealTupleType.SpatialEarth2DTuple) || 507 ref.equals(RealTupleType.LatitudeLongitudeTuple)) { 508 return ref; 509 } 510 } 511 return null; 512 } 513 514 private static TextDisplayable createValueDisplayer(final Color color) 515 throws VisADException, RemoteException 516 { 517 DecimalFormat fmt = new DecimalFormat(); 518 fmt.setMaximumIntegerDigits(3); 519 fmt.setMaximumFractionDigits(1); 520 TextDisplayable td = new TextDisplayable(TextType.Generic); 521 td.setLineWidth(2f); 522 td.setColor(color); 523 td.setTextSize(1.75f); 524 return td; 525 } 526 527 private static TupleType makeTupleType() { 528 TupleType t = null; 529 try { 530 t = new TupleType(new MathType[] {RealTupleType.SpatialEarth2DTuple, 531 TextType.Generic}); 532 } catch (Exception e) { 533 LogUtil.logException("HydraImageProbe.makeTupleType", e); 534 } 535 return t; 536 } 537 538 /** 539 * Respond to a change in the display's projection. In this case 540 * we fire the probePositionChanged() method with the probe's 541 * position. 542 */ 543 public void projectionChanged() { 544 super.projectionChanged(); 545 try { 546 probePositionChanged(getPosition()); 547 } catch (Exception exc) { 548 logException("projectionChanged", exc); 549 } 550 } 551 552 /** 553 * Make a menu for controlling the probe size, shape and position. 554 * 555 * @param probeMenu The menu to add to 556 * 557 * @return The menu 558 */ 559 public JMenu doMakeProbeMenu(JMenu probeMenu) { 560 JMenu posMenu = new JMenu("Position"); 561 probeMenu.add(posMenu); 562 posMenu.add(GuiUtils.makeMenuItem("Reset Probe Position", this, 563 "resetProbePosition")); 564 565 JMenu sizeMenu = new JMenu("Size"); 566 probeMenu.add(sizeMenu); 567 568 sizeMenu.add(GuiUtils.makeMenuItem("Increase", this, 569 "increaseProbeSize")); 570 sizeMenu.add(GuiUtils.makeMenuItem("Decrease", this, 571 "decreaseProbeSize")); 572 573 JMenu shapeMenu = new JMenu("Probe Shape"); 574 probeMenu.add(shapeMenu); 575 for (int i = 0; i < ShapeUtility.SHAPES.length; i++) { 576 TwoFacedObject tof = ShapeUtility.SHAPES[i]; 577 String lbl = tof.toString(); 578 if (Misc.equals(tof.getId(), marker)) { 579 lbl = ">" + lbl; 580 } 581 JMenuItem mi = GuiUtils.makeMenuItem(lbl, this, "setMarker", 582 tof.getId()); 583 shapeMenu.add(mi); 584 } 585 GuiUtils.limitMenuSize(shapeMenu, "Shape Group ", 10); 586 return probeMenu; 587 } 588 589 /** 590 * Increase the probe size 591 */ 592 public void increaseProbeSize() { 593 if (probe == null) { 594 return; 595 } 596 pointSize = probe.getPointScale(); 597 setPointSize(pointSize + pointSize * 0.5f); 598 } 599 600 601 /** 602 * Decrease the probe size 603 */ 604 public void decreaseProbeSize() { 605 if (probe == null) { 606 return; 607 } 608 pointSize = probe.getPointScale(); 609 pointSize = pointSize - pointSize * 0.5f; 610 if (pointSize < 0.1f) { 611 pointSize = 0.1f; 612 } 613 setPointSize(pointSize); 614 } 615 616 617 /** 618 * Set the PointSize property. 619 * 620 * @param value The new value for PointSize 621 */ 622 public void setPointSize(float value) { 623 pointSize = value; 624 if (probe != null) { 625 try { 626 probe.setAutoSize(false); 627 probe.setPointSize(pointSize); 628 probe.setAutoSize(true); 629 } catch (Exception exc) { 630 logException("Increasing probe size", exc); 631 } 632 } 633 } 634 635 /** 636 * Get the PointSize property. 637 * 638 * @return The PointSize 639 */ 640 public float getPointSize() { 641 return pointSize; 642 } 643 644 645 /** 646 * Get initial XY position from grid data. 647 * 648 * @return initial XY position of grid center point in VisAD space 649 * 650 * @throws RemoteException Java RMI problem 651 * @throws VisADException VisAD problem 652 */ 653 public RealTuple getGridCenterPosition() 654 throws VisADException, RemoteException { 655 RealTuple pos = new RealTuple(RealTupleType.SpatialCartesian2DTuple, 656 new double[] { 0, 657 0 }); 658 if (getGridDataInstance() != null) { 659 LatLonPoint rt = GridUtil.getCenterLatLonPoint( 660 getGridDataInstance().getGrid()); 661 RealTuple xyz = earthToBoxTuple(new EarthLocationTuple(rt, 662 new Real(RealType.Altitude, 0))); 663 if (xyz != null) { 664 pos = new RealTuple(new Real[] { (Real) xyz.getComponent(0), 665 (Real) xyz.getComponent(1) }); 666 } 667 } 668 return pos; 669 } 670 671 672 /** 673 * Get initial XY position from the screen 674 * 675 * @return initial XY position in VisAD space 676 * 677 * @throws RemoteException Java RMI problem 678 * @throws VisADException VisAD problem 679 */ 680 public RealTuple getInitialLinePosition() 681 throws VisADException, RemoteException { 682 //-double[] center = getScreenCenter(); 683 double[] center = new double[] {0,0}; 684 return new RealTuple(RealTupleType.SpatialCartesian2DTuple, 685 new double[] { center[0], 686 center[1] }); 687 } 688 689 /** 690 * Set the Marker property. 691 * 692 * @param value The new value for Marker 693 */ 694 public void setMarker(String value) { 695 marker = value; 696 if ((probe != null) && (marker != null)) { 697 try { 698 probe.setAutoSize(false); 699 probe.setAutoSize(true); 700 } catch (Exception exc) { 701 logException("Setting marker", exc); 702 } 703 } 704 } 705 706 /** 707 * Get the Marker property. 708 * 709 * @return The Marker 710 */ 711 public String getMarker() { 712 return marker; 713 } 714 715 /** 716 * Add any macro name/label pairs 717 * 718 * @param names List of macro names 719 * @param labels List of macro labels 720 */ 721 protected void getMacroNames(List names, List labels) { 722 super.getMacroNames(names, labels); 723 names.addAll(Misc.newList(MACRO_POSITION)); 724 labels.addAll(Misc.newList("Probe Position")); 725 } 726 727 /** 728 * Add any macro name/value pairs. 729 * 730 * 731 * @param template template 732 * @param patterns The macro names 733 * @param values The macro values 734 */ 735 protected void addLabelMacros(String template, List patterns, 736 List values) { 737 super.addLabelMacros(template, patterns, values); 738 patterns.add(MACRO_POSITION); 739 values.add(positionText); 740 } 741 742 /** 743 * This method is called to update the legend labels when 744 * some state has changed in this control that is reflected in the labels. 745 */ 746 protected void updateLegendLabel() { 747 super.updateLegendLabel(); 748 // if the display label has the position, we'll update the list also 749 String template = getDisplayListTemplate(); 750 if (template.contains(MACRO_POSITION)) { 751 updateDisplayList(); 752 } 753 } 754 755 /** 756 * Append any label information to the list of labels. 757 * 758 * @param labels in/out list of labels 759 * @param legendType The type of legend, BOTTOM_LEGEND or SIDE_LEGEND 760 */ 761 public void getLegendLabels(List labels, int legendType) { 762 super.getLegendLabels(labels, legendType); 763 labels.add(positionText); 764 } 765} 766