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