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