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