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