001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data.hydra;
030
031import java.awt.Color;
032import java.rmi.RemoteException;
033
034import ucar.unidata.util.Range;
035import ucar.visad.display.DisplayableData;
036import ucar.visad.display.ScalarMapSet;
037
038import visad.BadMappingException;
039import visad.BaseColorControl;
040import visad.ConstantMap;
041import visad.DataRenderer;
042import visad.Display;
043import visad.RangeControl;
044import visad.RealType;
045import visad.ScalarMap;
046import visad.ScalarMapControlEvent;
047import visad.ScalarMapEvent;
048import visad.ScalarMapListener;
049import visad.Unit;
050import visad.VisADException;
051import visad.bom.ImageRendererJ3D;
052import visad.java3d.DefaultRendererJ3D;
053
054import edu.wisc.ssec.mcidasv.control.HydraControl;
055
056public class HydraRGBDisplayable extends DisplayableData {
057
058    /**
059     * The name of the "color palette" property.
060     */
061    public static final String COLOR_PALETTE = "colorPalette";
062
063    /**
064     * The name of the "RGB real-type" property.
065     */
066    public static final String RGB_REAL_TYPE = "rgbRealType";
067
068    /**
069     * The polygon fill style
070     */
071    public static final int POLYGON_FILL = 0;
072
073    /**
074     * The polygon line style
075     */
076    public static final int POLYGON_LINE = 1;
077
078    /**
079     * The polygon point style
080     */
081    public static final int POLYGON_POINT = 2;
082
083    /**
084     * Color Palette
085     */
086    private float[][] colorPalette = null;
087
088    private String colorPaletteName = null;
089
090    /** color ScalarMap */
091    private volatile ScalarMap colorMap;
092
093    /** field index to Animation ScalarMap */
094    private volatile ScalarMap animMap;
095
096    /** control for ScalarMap */
097    private volatile BaseColorControl colorControl;
098
099    /** RealType for the ScalarMap */
100    private volatile RealType rgbRealType;
101
102    /** RealType for the SelectRange ScalarMap */
103    private ScalarMap selectMap = null;
104
105    /** RealType for the Animation ScalarMap */
106    private RealType indexRealType;
107
108    /** Control for select range */
109    private RangeControl selectControl;
110
111    /** RealType for the SelectRange ScalarMap */
112    private RealType selectRealType = null;
113
114    /** flag for whether alpha is used or not */
115    private boolean alphaflag;
116
117    /** local point size */
118    private float myPointSize;
119
120    /** low range for colors */
121    //private double lowRange = 315;           // low range for scalarmap
122    private double lowRange = Double.NaN;           // low range for scalarmap
123
124    /** high range for colors */
125    //private double highRange = 230;          // high range for scalarmap
126    private double highRange = Double.NaN;          // high range for scalarmap
127
128    /** default polygonMode */
129    private int polygonMode = POLYGON_FILL;
130
131    /** default curvedSize */
132    private int curvedSize = 10;
133
134    /** low range for select */
135    private double lowSelectedRange = Double.NaN;   // low range for scalarmap
136
137    /** high range for select */
138    private double highSelectedRange = Double.NaN;  // high range for scalarmap
139
140    /** low range for select map */
141    private double minSelect = Double.NaN;          // low range for scalarmap
142
143    /** high range for select map */
144    private double maxSelect = Double.NaN;          // high range for scalarmap
145
146    private HydraControl multiSpecCntrl;
147
148    private boolean useDefaultRenderer = false;
149
150    /**
151     * Constructs from a name for the Displayable and the type of the
152     * RGB parameter.
153     *
154     * @param name              The name for the displayable.
155     * @param rgbRealType       The type of the RGB parameter.  May be
156     *                          {@code null}.
157     * @param alphaflag         boolean - will use Display.RBGA if true
158     *                            otherwise only Display.RGB
159     * @throws VisADException   VisAD failure.
160     * @throws RemoteException  Java RMI failure.
161     */
162    public HydraRGBDisplayable(String name, RealType rgbRealType, RealType indexRealType, boolean alphaflag, 
163                 HydraControl multiSpecCntrl)
164            throws VisADException, RemoteException {
165        this(name, rgbRealType, indexRealType, null, alphaflag, null, multiSpecCntrl);
166    }
167
168    public HydraRGBDisplayable(String name, RealType rgbRealType, RealType indexRealType, float[][] colorPalette, boolean alphaflag, Range initRange,
169                   HydraControl multiSpecCntrl)
170            throws VisADException, RemoteException {
171        this(name, rgbRealType, indexRealType, colorPalette, null, alphaflag, initRange, multiSpecCntrl);
172    }
173
174    /**
175     * Constructs from a name for the Displayable and the type of the
176     * RGB parameter.
177     *
178     * @param name              The name for the displayable.
179     * @param rgbRealType       The type of the RGB parameter.  May be
180     *                          {@code null}.
181     * @param colorPalette      The initial colorPalette to use. May be
182     *                          {@code null} (Vis5D palette used
183     *                          as default).
184     * @param alphaflag         boolean - use Display.RBGA if true
185     * @param initRange         Range to use as initial or first min,max
186     * @throws VisADException   VisAD failure.
187     * @throws RemoteException  Java RMI failure.
188     */
189    public HydraRGBDisplayable(String name, RealType rgbRealType, RealType indexRealType, float[][] colorPalette, String colorPaletteName, boolean alphaflag, Range initRange,
190                   HydraControl multiSpecCntrl)
191            throws VisADException, RemoteException {
192
193        super(name);
194        
195        this.rgbRealType  = rgbRealType;
196        this.selectRealType = rgbRealType;
197        this.indexRealType  = indexRealType;
198        this.colorPalette = colorPalette;
199        this.colorPaletteName = colorPaletteName;
200        this.alphaflag    = alphaflag;
201        this.multiSpecCntrl = multiSpecCntrl;
202
203        if (initRange != null) {
204          this.lowRange = initRange.getMin();
205          this.highRange = initRange.getMax();
206        }
207
208        if (rgbRealType != null) {
209            setColorMaps();
210            if (useDisplayUnitForColor()) {
211                setDisplayUnit(rgbRealType.getDefaultUnit());
212            } else {
213                setColorUnit(rgbRealType.getDefaultUnit());
214            }
215        }
216
217        if (indexRealType != null) {
218          //-setAnimationMap();
219          setSelectMap();
220        }
221
222        if (selectRealType != null) {
223          //setSelectMaps();
224        }
225    }
226
227    /**
228     * Does this object use the displayUnit (or the colorUnit) for its
229     * display unit. The default is true.  This allows derived classes
230     * to have this class use the colorUnit.
231     * @return  true if the display unit is the same as the color unit
232     */
233    protected boolean useDisplayUnitForColor() {
234        return true;
235    }
236
237
238    /**
239     * Constructs from another instance.  The following attributes are set from
240     * the other instance: color palette, the color RealType.
241     * @param that              The other instance.
242     * @throws VisADException   VisAD failure.
243     * @throws RemoteException  Java RMI failure.
244     */
245    protected HydraRGBDisplayable(HydraRGBDisplayable that)
246            throws VisADException, RemoteException {
247
248        super(that);
249        colorPalette = that.colorPalette;
250        rgbRealType  = that.rgbRealType;  // immutable object
251        alphaflag    = that.alphaflag;
252
253        if (rgbRealType != null) {
254            setColorMaps();
255        }
256    }
257
258    /**
259     * Sets the RealType of the RGB parameter.
260     * @param realType          The RealType of the RGB parameter.  May
261     *                          not be {@code null}.
262     * @throws VisADException   VisAD failure.
263     * @throws RemoteException  Java RMI failure.
264     */
265    public void setRGBRealType(RealType realType)
266            throws RemoteException, VisADException {
267
268        if ( !realType.equals(rgbRealType)) {
269            RealType oldValue = rgbRealType;
270            rgbRealType = realType;
271            setColorMaps();
272            if (useDisplayUnitForColor()) {
273                if ( !isUnitCompatible(rgbRealType, getDisplayUnit())) {
274                    setDisplayUnit(null);
275                }
276            } else {
277                if ( !isUnitCompatible(rgbRealType, getColorUnit())) {
278                    setColorUnit(null);
279                }
280            }
281            firePropertyChange(RGB_REAL_TYPE, oldValue, rgbRealType);
282        }
283    }
284
285    public ScalarMap getColorMap() {
286      return colorMap;
287    }
288
289    public ScalarMap getAnimationMap() {
290      return animMap;
291    }
292
293
294    /**
295     * Returns the RealType of the RGB parameter.
296     * @return                  The RealType of the color parameter.  May
297     *                          be {@code null}.
298     */
299    public RealType getRGBRealType() {
300        return rgbRealType;
301    }
302
303    /**
304     * Returns the RealType of the SelectRange parameter.
305     * @return                  The RealType of the select range parameter.  May
306     *                          be {@code null}.
307     */
308    public RealType getSelectRealType() {
309        return selectRealType;
310    }
311
312    protected DataRenderer getDataRenderer() throws VisADException {
313      if (useDefaultRenderer) {
314        return new DefaultRendererJ3D();
315      }
316      else {
317        return new ImageRendererJ3D();
318      }
319    }
320
321    public void setDefaultRenderer() {
322      useDefaultRenderer = true;
323    }
324
325    public void setImageRenderer() {
326      useDefaultRenderer = false;
327    }
328
329    /**
330     * Sets the set of ScalarMap-s of this instance.  The ScalarMap-s of
331     * this instance will be added to the set before the SCALAR_MAP_SET
332     * property is set.  This method fires a PropertyChangeEvent for
333     * SCALAR_MAP_SET with {@code null} for the old value and the new
334     * set of ScalarMap-s for the new Value.  Intermediate subclasses that
335     * have their own ScalarMap-s should override this method and invoke
336     * {@code super.setScalarMaps(ScalarMapSet)}.
337     * @param maps              The set of ScalarMap-s to be added.
338     * @throws BadMappingException      The RealType of the color parameter
339     *                          has not been set or its ScalarMap is alread in
340     *                          the set.
341     */
342    protected void setScalarMaps(ScalarMapSet maps)
343            throws BadMappingException {
344
345        if (colorMap == null) {
346            throw new BadMappingException(getClass().getName()
347                                          + ".setScalarMaps(ScalarMapSet): "
348                                          + "Color not yet set");
349        }
350
351        maps.add(colorMap);
352
353        if (selectMap != null) {
354
355            maps.add(selectMap);
356        }
357
358        super.setScalarMapSet(maps);
359    }
360
361    /**
362     * This method sets the color palette
363     * according to the color table in argument;
364     * pair this method with setRange(lo,high) to get
365     * a fixed association of color table and range of values.
366     *
367     * @param colorPalette Color table or color-alpha table desired.
368     * @param name Name for the color table (can be {@code null}).
369     * @throws VisADException  if a core VisAD failure occurs.
370     * @throws RemoteException if a Java RMI failure occurs.
371     */
372    public void setColorPalette(float[][] colorPalette, String name)
373            throws RemoteException, VisADException {
374        if (colorControl != null) {
375            colorControl.setTable(colorPalette);
376        }
377
378        this.colorPalette = colorPalette;
379        this.colorPaletteName = name;
380    }
381
382    /**
383     * This method sets the color palette
384     * according to the color table in argument;
385     * pair this method with setRange(lo,high) to get
386     * a fixed association of color table and range of values;
387     * asigns null (doesn't have a name) for the name.
388     *
389     * @param colorPalette     the color table or color-alpha table desired
390     * @throws VisADException  if a core VisAD failure occurs.
391     * @throws RemoteException if a Java RMI failure occurs.
392     */
393    public void setColorPalette(float[][] colorPalette)
394            throws VisADException, RemoteException {
395        setColorPalette(colorPalette, null);
396    }
397
398    /**
399     * Return the current color palette in this Displayable
400     *
401     * @return a color table float[3][len] or color-alpha table float[4][len]
402     */
403    public float[][] getColorPalette() {
404        return colorPalette;
405    }
406
407    public String getColorPaletteName() {
408        return colorPaletteName;
409    }
410
411    /**
412     * Make a color palette representing this color and set it as the
413     * color pallete.
414     *
415     * @param  color  color to use
416     * @throws VisADException     VisAD failure.
417     * @throws RemoteException    Java RMI failure.
418     */
419    public void setColor(Color color) throws RemoteException, VisADException {
420        int       len   = 5;
421        float[][] table = new float[(alphaflag == true)
422                                    ? 4
423                                    : 3][len];
424        for (int m = 0; m < len; m++) {
425            table[0][m] = color.getRed() / 255.f;    // Red amount  
426            table[1][m] = color.getGreen() / 255.f;  // Green
427            table[2][m] = color.getBlue() / 255.f;   // Blue  
428        }
429        setColorPalette(table);
430    }
431
432    /**
433     * This method sets the color palette to shades of grey.
434     *
435     * @throws VisADException  if a core VisAD failure occurs.
436     * @throws RemoteException if a Java RMI failure occurs.
437     */
438    public final void setGreyPalette()
439            throws RemoteException, VisADException {
440
441        if (colorControl != null) {
442            colorControl.initGreyWedge();
443            setColorPalette(colorControl.getTable());
444        }
445    }
446
447    /**
448     * This method with no argument sets the default Vis5D color spectrum.
449     *
450     * @throws VisADException  if a core VisAD failure occurs.
451     * @throws RemoteException if a Java RMI failure occurs.
452     */
453    public final void setVisADPalette()
454            throws RemoteException, VisADException {
455
456        if (colorControl != null) {
457            colorControl.initVis5D();
458            setColorPalette(colorControl.getTable());
459        }
460    }
461
462    /**
463     * Set the upper and lower limit of the range values associated
464     * with a color table.
465     *
466     * @param low    the minimun value
467     * @param hi     the maximum value
468     * @deprecated   use setRangeForColor
469     *
470     * @throws RemoteException  Java RMI error
471     * @throws VisADException   problem creating VisAD object
472     */
473    public void setRange(double low, double hi)
474            throws VisADException, RemoteException {
475
476        setRangeForColor(low, hi);
477    }
478
479    /**
480     * Set the upper and lower limit of the range values associated
481     * with a color table.
482     *
483     * Matches method name in Contour2DDisplayable
484     *
485     * @param low               The minimum value of the parameter matched to
486     *                          the low end of the color table.
487     * @param hi                The maximum value of the parameter matched to
488     *                          the high end of the color table.
489     *
490     * @exception VisADException   VisAD failure.
491     * @exception RemoteException  Java RMI failure.
492     */
493    public void setRangeForColor(double low, double hi)
494            throws VisADException, RemoteException {
495        lowRange  = low;
496        highRange = hi;
497        if ((colorMap != null) && hasRange()) {
498            colorMap.setRange(low, hi);
499        }
500    }
501
502    /**
503     * Get the color range
504     *
505     * @return an array of the low and high values for the range
506     * @deprecated  use #getRangeForColor()
507     */
508    public double[] getRangeforColor() {
509        return getRangeForColor();
510    }
511
512    /**
513     * Get the color range
514     *
515     * @return an array of the low and high values for the range
516     */
517    public double[] getRangeForColor() {
518        return new double[]{ lowRange, highRange };
519    }
520
521    /**
522     * Apply the correct unit (either the displayUnit or the colorUnit)
523     * to the scalar map
524     *
525     * @param colorMap   ScalarMap to apply to
526     * @param rgbRealType  RealType for default Unit
527     *
528     * @throws RemoteException  Java RMI error
529     * @throws VisADException   problem creating VisAD object
530     */
531    private void applyUnit(ScalarMap colorMap, RealType rgbRealType)
532            throws VisADException, RemoteException {
533        if (useDisplayUnitForColor()) {
534            applyDisplayUnit(colorMap, rgbRealType);
535        } else {
536            applyColorUnit(colorMap, rgbRealType);
537        }
538    }
539
540
541    /**
542     * Set the units for the displayed range
543     *
544     * @param unit Unit for display
545     *
546     * @throws RemoteException  Java RMI error
547     * @throws VisADException   problem creating VisAD object
548     */
549    public void setDisplayUnit(Unit unit)
550            throws VisADException, RemoteException {
551        if (useDisplayUnitForColor()) {
552            //Make sure this unit is ok
553            checkUnit(rgbRealType, unit);
554        }
555        super.setDisplayUnit(unit);
556        if (useDisplayUnitForColor()) {
557            applyUnit(colorMap, rgbRealType);
558        }
559    }
560
561
562    /**
563     * Set the units for the displayed range
564     *
565     * @param unit Unit for display
566     *
567     * @throws RemoteException  Java RMI error
568     * @throws VisADException   problem creating VisAD object
569     */
570    public void setColorUnit(Unit unit)
571            throws VisADException, RemoteException {
572        if ( !useDisplayUnitForColor()) {
573            //Make sure this unit is ok
574            checkUnit(rgbRealType, unit);
575        }
576        super.setColorUnit(unit);
577        if ( !useDisplayUnitForColor()) {
578            applyUnit(colorMap, rgbRealType);
579        }
580    }
581
582
583    /**
584     * Returns whether this Displayable has a valid range (i.e., lowRange and
585     * highRange are both not NaN's
586     *
587     * @return true if range has been set
588     */
589    public boolean hasRange() {
590        return ( !Double.isNaN(lowRange) && !Double.isNaN(highRange));
591    }
592
593
594    /**
595     * Sets the size of points in this Displayable.
596     *
597     * @param   pointSize     Size of points (2 = normal)
598     *
599     * @throws VisADException     VisAD failure.
600     * @throws RemoteException    Java RMI failure.
601     */
602    public void setPointSize(float pointSize)
603            throws VisADException, RemoteException {
604
605        synchronized (this) {
606            addConstantMap(new ConstantMap(pointSize, Display.PointSize));
607            myPointSize = pointSize;
608        }
609
610    }
611
612    /**
613     * Gets the point size associated with this LineDrawing
614     *
615     * @return  point size
616     */
617    public float getPointSize() {
618        return myPointSize;
619    }
620
621    /**
622     * Set the type of polygon display that should be used
623     *
624     * @param polygonMode  polygon mode
625     *
626     * @throws RemoteException  Java RMI error
627     * @throws VisADException   problem creating VisAD object
628     */
629    public void setPolygonMode(int polygonMode)
630            throws VisADException, RemoteException {
631        this.polygonMode = polygonMode;
632        addConstantMap(new ConstantMap(convertToVisADPolygonMode(polygonMode),
633                                       Display.PolygonMode));
634    }
635
636    /**
637     * Converts an RGBDisplayable Polygon mode to the appropriate
638     * (or default) VisAD mode
639     *
640     * @param myMode  polygon mode
641     * @return  Java3D mode
642     */
643    private int convertToVisADPolygonMode(int myMode) {
644        if (visad.util.Util.canDoJava3D()) {
645            switch (myMode) {
646
647              case POLYGON_FILL :
648                  return visad.java3d.DisplayImplJ3D.POLYGON_FILL;
649
650              case POLYGON_LINE :
651                  return visad.java3d.DisplayImplJ3D.POLYGON_LINE;
652
653              case POLYGON_POINT :
654                  return visad.java3d.DisplayImplJ3D.POLYGON_POINT;
655
656              default :
657                  return visad.java3d.DisplayImplJ3D.POLYGON_FILL;
658            }
659        } else {
660            return 0;
661        }
662    }
663
664    /**
665     * Return the type of polygon mode being used
666     *
667     * @return polygon mode
668     */
669    public int getPolygonMode() {
670        return polygonMode;
671    }
672
673    /**
674     * Set the curved size for textured displays
675     *
676     * @param curvedSize size to use (&gt; 0)
677     *
678     * @throws RemoteException  Java RMI error
679     * @throws VisADException   problem creating VisAD object
680     */
681    public void setCurvedSize(int curvedSize)
682            throws VisADException, RemoteException {
683        this.curvedSize = curvedSize;
684        addConstantMap(makeCurvedSizeMap(curvedSize));
685    }
686
687    /**
688     * Create the ConstantMap for the texture curve size
689     *
690     * @param curvedSize   size for texture curve
691     * @return  ConstantMap
692     *
693     * @throws RemoteException  Java RMI error
694     * @throws VisADException   problem creating VisAD object
695     */
696    protected ConstantMap makeCurvedSizeMap(int curvedSize)
697            throws VisADException, RemoteException {
698        return new ConstantMap(curvedSize, Display.CurvedSize);
699    }
700
701    /**
702     * Return the size of a curved texture
703     * @return curved size
704     */
705    public int getCurvedSize() {
706        return curvedSize;
707    }
708
709    /**
710     * creates the ScalarMap for color and ColorControl for this Displayable.
711     *
712     * @throws VisADException   VisAD failure.
713     * @throws RemoteException  Java RMI failure.
714     */
715    private void setColorMaps() throws RemoteException, VisADException {
716
717        // ScalarMap is either mapping to Display.RGB (color only)
718        // or to Display.RGBA color plus transparency.
719        if ( !alphaflag) {
720            colorMap = new ScalarMap(rgbRealType, Display.RGB);
721        } else {
722            colorMap = new ScalarMap(rgbRealType, Display.RGBA);
723        }
724
725        applyUnit(colorMap, rgbRealType);
726
727        if (hasRange()) {
728           colorMap.setRange(lowRange, highRange);
729        }
730
731        colorMap.addScalarMapListener(new ScalarMapListener() {
732
733            public void controlChanged(ScalarMapControlEvent event)
734                    throws RemoteException, VisADException {
735
736                int id = event.getId();
737
738                if ((id == ScalarMapEvent.CONTROL_ADDED)
739                        || (id == ScalarMapEvent.CONTROL_REPLACED)) {
740                    colorControl = (BaseColorControl) colorMap.getControl();
741
742                    if (colorControl != null) {
743                        if (colorPalette != null) {
744                            colorControl.setTable(colorPalette);
745                        } else {
746                            colorPalette = colorControl.getTable();
747                        }
748                    }
749                }
750            }
751
752            public void mapChanged(ScalarMapEvent event)
753                    throws RemoteException, VisADException {
754                if ((event.getId() == ScalarMapEvent.AUTO_SCALE) && hasRange()) {
755                  double[] rng = colorMap.getRange();
756                  if (multiSpecCntrl != null) {
757                    multiSpecCntrl.updateRange(new Range(rng));
758                  }
759                }
760            }
761        });
762        ScalarMapSet maps = getScalarMapSet();  //new ScalarMapSet();
763        maps.add(colorMap);
764        setScalarMapSet(maps);
765    }
766
767    private void setSelectMap() throws RemoteException, VisADException {
768      animMap = new ScalarMap(indexRealType, Display.SelectValue);
769      ScalarMapSet maps = getScalarMapSet();
770      maps.add(animMap);
771      setScalarMapSet(maps);
772    }
773
774    /**
775     * Sets the RealType of the select parameter.
776     * @param realType          The RealType of the RGB parameter.  May
777     *                          not be {@code null}.
778     * @throws VisADException   VisAD failure.
779     * @throws RemoteException  Java RMI failure.
780     */
781    protected void setSelectRealType(RealType realType)
782            throws RemoteException, VisADException {
783
784        if ( !realType.equals(selectRealType)) {
785            selectRealType = realType;
786            setSelectMaps();
787            if (useDisplayUnitForColor()) {
788                if ( !isUnitCompatible(selectRealType, getDisplayUnit())) {
789                    setDisplayUnit(null);
790                }
791            } else {
792                if ( !isUnitCompatible(selectRealType, getColorUnit())) {
793                    setColorUnit(null);
794                }
795            }
796        }
797    }
798
799    /**
800     * Returns whether this Displayable has a valid range
801     * (i.e., lowSelectedRange and highSelectedRange are both not NaN's
802     *
803     * @return true if range has been set
804     */
805    public boolean hasSelectedRange() {
806        return ( !Double.isNaN(lowSelectedRange)
807                 && !Double.isNaN(highSelectedRange));
808    }
809
810    /**
811     * Set selected range with the range for select
812     *
813     * @param low  low select value
814     * @param hi   hi select value
815     *
816     * @throws RemoteException  Java RMI error
817     * @throws VisADException   problem creating VisAD object
818     */
819    public void setSelectedRange(double low, double hi)
820            throws VisADException, RemoteException {
821
822        lowSelectedRange  = low;
823        highSelectedRange = hi;
824        if ((selectControl != null) && hasSelectedRange()) {
825            selectControl.setRange(new double[]{ low, hi });
826        }
827
828    }
829
830    /**
831     * Set the upper and lower limit of the range values associated
832     * with a color table.
833     *
834     * @param low    the minimun value
835     * @param hi     the maximum value
836     *
837     * @throws RemoteException  Java RMI error
838     * @throws VisADException   problem creating VisAD object
839     */
840    public void setRangeForSelect(double low, double hi)
841            throws VisADException, RemoteException {
842
843        minSelect = low;
844        maxSelect = hi;
845        if ((selectMap != null) && hasSelectMinMax()) {
846            selectMap.setRange(low, hi);
847        }
848    }
849
850    /**
851     * Check to see if the range has been set for the select
852     *
853     * @return true if it has
854     */
855    private boolean hasSelectMinMax() {
856        return ( !Double.isNaN(minSelect) && !Double.isNaN(maxSelect));
857    }
858
859    /**
860     * creates the ScalarMap for SelectRange and control for this Displayable.
861     *
862     * @throws VisADException   VisAD failure.
863     * @throws RemoteException  Java RMI failure.
864     */
865    private void setSelectMaps() throws RemoteException, VisADException {
866
867        selectMap = new ScalarMap(selectRealType, Display.SelectRange);
868
869        if (selectRealType.equals(rgbRealType)) {
870            applyUnit(selectMap, selectRealType);
871        }
872
873        if (hasSelectMinMax()) {
874            selectMap.setRange(minSelect, maxSelect);
875        }
876
877        selectMap.addScalarMapListener(new ScalarMapListener() {
878
879            public void controlChanged(ScalarMapControlEvent event)
880                    throws RemoteException, VisADException {
881
882                int id = event.getId();
883
884                if ((id == ScalarMapEvent.CONTROL_ADDED)
885                        || (id == ScalarMapEvent.CONTROL_REPLACED)) {
886                    selectControl = (RangeControl) selectMap.getControl();
887                    if (hasSelectedRange()) {
888                        selectControl.setRange(new double[]{ lowSelectedRange,
889                                                             highSelectedRange });
890                    }
891                }
892            }
893
894            public void mapChanged(ScalarMapEvent event)
895                    throws RemoteException, VisADException {
896                if ((event.getId() == ScalarMapEvent.AUTO_SCALE)
897                        && hasSelectMinMax()) {
898                    selectMap.setRange(minSelect, maxSelect);
899                }
900            }
901        });
902        ScalarMapSet maps = getScalarMapSet();  //new ScalarMapSet();
903        maps.add(selectMap);
904        setScalarMapSet(maps);
905    }
906
907}