001 /*
002 * $Id: Grid2DReadoutProbe.java,v 1.11 2012/02/19 17:35:37 davep Exp $
003 *
004 * This file is part of McIDAS-V
005 *
006 * Copyright 2007-2012
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
031 package edu.wisc.ssec.mcidasv.control;
032
033
034 import java.awt.Color;
035 import java.awt.event.ActionEvent;
036 import java.awt.event.ActionListener;
037 import java.beans.PropertyChangeEvent;
038 import java.rmi.RemoteException;
039 import java.text.DecimalFormat;
040 import java.util.List;
041
042 import javax.swing.JMenu;
043 import javax.swing.JMenuItem;
044
045 import visad.CoordinateSystem;
046 import visad.Data;
047 import visad.DataReference;
048 import visad.DataReferenceImpl;
049 import visad.DisplayEvent;
050 import visad.DisplayListener;
051 import visad.FlatField;
052 import visad.FunctionType;
053 import visad.MathType;
054 import visad.Real;
055 import visad.RealTuple;
056 import visad.RealTupleType;
057 import visad.RealType;
058 import visad.Text;
059 import visad.TextType;
060 import visad.Tuple;
061 import visad.TupleType;
062 import visad.VisADException;
063 import visad.georef.EarthLocationTuple;
064 import visad.georef.LatLonPoint;
065
066 import ucar.unidata.collab.Sharable;
067 import ucar.unidata.data.grid.GridUtil;
068 import ucar.unidata.idv.ViewDescriptor;
069 import ucar.unidata.idv.control.GridDisplayControl;
070 import ucar.unidata.util.GuiUtils;
071 import ucar.unidata.util.LogUtil;
072 import ucar.unidata.util.Misc;
073 import ucar.unidata.util.TwoFacedObject;
074 import ucar.unidata.view.geoloc.NavigatedDisplay;
075 import ucar.visad.ShapeUtility;
076 import ucar.visad.display.DisplayMaster;
077 import ucar.visad.display.PointProbe;
078 import ucar.visad.display.SelectorDisplayable;
079 import 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.11 $Date: 2012/02/19 17:35:37 $
091 */
092 public 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