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