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