001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2025
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
029
030package edu.wisc.ssec.mcidasv.control;
031
032import java.awt.BorderLayout;
033import java.awt.Color;
034import java.awt.Component;
035import java.awt.Container;
036
037import java.awt.FlowLayout;
038import java.awt.GridLayout;
039import java.awt.event.ActionEvent;
040import java.awt.event.ActionListener;
041import java.awt.geom.Rectangle2D;
042import java.net.URL;
043import java.rmi.RemoteException;
044import java.util.ArrayList;
045import java.util.Hashtable;
046import java.util.List;
047
048import javax.swing.ButtonGroup;
049import javax.swing.ImageIcon;
050import javax.swing.JComponent;
051import javax.swing.JLabel;
052import javax.swing.JPanel;
053import javax.swing.JRadioButton;
054import javax.swing.JToggleButton;
055import javax.swing.JButton;
056import javax.swing.border.CompoundBorder;
057import javax.swing.border.EmptyBorder;
058import javax.swing.border.LineBorder;
059
060import org.slf4j.Logger;
061import org.slf4j.LoggerFactory;
062
063import ucar.unidata.idv.ControlContext;
064import visad.AxisScale;
065import visad.BaseColorControl;
066import visad.CellImpl;
067import visad.CoordinateSystem;
068import visad.Data;
069import visad.DelaunayCustom;
070import visad.DisplayEvent;
071import visad.DisplayListener;
072
073import visad.FieldImpl;
074import visad.FlatField;
075import visad.FunctionType;
076import visad.Gridded2DSet;
077import visad.Gridded3DSet;
078import visad.Integer1DSet;
079import visad.Linear2DSet;
080import visad.LinearLatLonSet;
081import visad.RealTupleType;
082import visad.MathType;
083import visad.RealType;
084import visad.SampledSet;
085import visad.ScalarMap;
086import visad.Set;
087import visad.SetType;
088import visad.UnionSet;
089import visad.VisADException;
090import visad.data.mcidas.BaseMapAdapter;
091import visad.georef.MapProjection;
092import visad.georef.TrivialMapProjection;
093import visad.python.JPythonMethods;
094
095import ucar.unidata.data.DataAlias;
096import ucar.unidata.data.DataChoice;
097import ucar.unidata.data.DataSelection;
098import ucar.unidata.data.grid.GridUtil;
099import ucar.unidata.idv.DisplayConventions;
100import ucar.unidata.idv.control.ColorTableWidget;
101import ucar.unidata.idv.control.DisplayControlImpl;
102import ucar.unidata.ui.colortable.ColorTableManager;
103import ucar.unidata.util.ColorTable;
104import ucar.unidata.util.LogUtil;
105import ucar.unidata.util.Range;
106import ucar.unidata.view.geoloc.MapProjectionDisplay;
107import ucar.unidata.view.geoloc.MapProjectionDisplayJ3D;
108import ucar.visad.display.DisplayMaster;
109import ucar.visad.display.LineDrawing;
110import ucar.visad.display.MapLines;
111import ucar.visad.display.RGBDisplayable;
112import ucar.visad.display.RubberBandBox;
113import ucar.visad.display.XYDisplay;
114
115import edu.wisc.ssec.mcidasv.data.hydra.CurveDrawer;
116import edu.wisc.ssec.mcidasv.data.hydra.HistogramField;
117import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
118import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
119import edu.wisc.ssec.mcidasv.data.hydra.SubsetRubberBandBox;
120import edu.wisc.ssec.mcidasv.data.hydra.LongitudeLatitudeCoordinateSystem;
121import edu.wisc.ssec.mcidasv.data.StatsTable;
122
123public class ScatterDisplay extends DisplayControlImpl {
124
125    private static final Logger logger =
126        LoggerFactory.getLogger(ScatterDisplay.class);
127
128    private Container container;
129    private FlatField X_field;
130    private FlatField Y_field;
131    private FlatField Area_field;
132    private double total_area;
133    private DisplayMaster scatterMaster = null;
134
135    private DisplayMaster dspMasterX;
136    private DisplayMaster dspMasterY;
137
138    private HistogramField histoField;
139
140    private FlatField mask_field;
141    private float[][] mask_range;
142    private float[][] scatterFieldRange;
143    private Data X_data;
144    private Data Y_data;
145    private String X_name;
146    private String Y_name;
147    
148    private boolean cancel = false;
149
150    private ScatterDisplayable scatterMarkDsp;
151
152    private BoxCurveSwitch boxCurveSwitch;
153
154    public DataChoice dataChoiceX = null;
155
156    public DataChoice dataChoiceY = null;
157
158    public DataSelection dataSelectionX = null;
159
160    public DataSelection dataSelectionY = null;
161
162    JComponent ctwCompX;
163
164    JComponent ctwCompY;
165
166    ColorTableWidget ctw;
167
168    int n_selectors = 3;
169
170    List<ScatterBoxSelector> scatterBoxSelectors = new ArrayList<>();
171
172    List<ScatterCurveSelector> scatterCurveSelectors = new ArrayList<>();
173
174    List<ImageBoxSelector> imageXBoxSelectors = new ArrayList<>();
175
176    List<ImageBoxSelector> imageYBoxSelectors = new ArrayList<>();
177
178    List<ImageCurveSelector> imageXCurveSelectors = new ArrayList<>();
179
180    List<ImageCurveSelector> imageYCurveSelectors = new ArrayList<>();
181
182    JToggleButton[] selectorToggleButtons = new JToggleButton[n_selectors];
183
184    Color[] selectorColors = new Color[] {
185        Color.magenta,
186        Color.green,
187        Color.blue
188    };
189
190    float[][] maskColorPalette = new float[][] {
191        { 0.8f, 0.0f, 0.0f },
192        { 0.0f, 0.8f, 0.0f },
193        { 0.8f, 0.0f, 0.8f }
194    };
195
196    float[][] markPaletteBlackBackground = new float[][] {
197        { 1.0f, 0.8f, 0.0f, 0.0f },
198        { 1.0f, 0.0f, 0.8f, 0.0f },
199        { 1.0f, 0.8f, 0.0f, 0.8f }
200    };
201
202    float[][] markPaletteWhiteBackground = new float[][] {
203        { 0.0f, 0.8f, 0.0f, 0.0f },
204        { 0.0f, 0.0f, 0.8f, 0.0f },
205        { 0.0f, 0.8f, 0.0f, 0.8f }
206    };
207
208    /** used for persistence */
209    private boolean blackBackground = true;
210    
211    JRadioButton bgColorBlack;
212    
213    JRadioButton bgColorWhite;
214    
215    ButtonGroup bgColorGroup;
216    
217    JButton computeStatsButton;
218    
219    StatsTable statsTable;
220   
221    boolean selectByCurve = false;
222
223    public ScatterDisplay() {
224      super();
225      setHelpUrl("idv.controls.misc.scatteranalysiscontrol");
226    }
227    
228
229    @Override public boolean init(List choices) throws VisADException, RemoteException {
230        bgColorBlack = new JRadioButton("Black");
231        bgColorBlack.addActionListener(e -> {
232            scatterMaster.setForeground(Color.white);
233            scatterMaster.setBackground(Color.black);
234            setBlackBackground(true);
235            try {
236                scatterMarkDsp.setColorPalette(markPaletteBlackBackground);
237            } catch (Exception ex) {
238                logger.error("could not change color palette", ex);
239            }
240        });
241
242        bgColorWhite = new JRadioButton("White");
243        bgColorWhite.addActionListener(e -> {
244            scatterMaster.setForeground(Color.black);
245            scatterMaster.setBackground(Color.white);
246            setBlackBackground(false);
247            try {
248                scatterMarkDsp.setColorPalette(markPaletteWhiteBackground);
249            } catch (Exception ex) {
250                logger.error("could not change color palette", ex);
251            }
252        });
253
254        bgColorGroup = new ButtonGroup();
255        bgColorGroup.add(bgColorBlack);
256        bgColorGroup.add(bgColorWhite);
257
258        bgColorBlack.setSelected(getBlackBackground());
259        bgColorWhite.setSelected(!getBlackBackground());
260        
261        if ((dataChoiceX != null) && (dataChoiceY != null)) {
262          setupFromUnpersistence();
263        } else {
264            try {
265                setup();
266            } catch (VisADException vade) {
267                return false;
268            }
269        }
270
271        mask_field = new FlatField(
272             new FunctionType(((FunctionType)X_field.getType()).getDomain(), RealType.Generic),
273                  X_field.getDomainSet());
274
275        int len = X_field.getDomainSet().getLength();
276        int[] lens = ((Gridded2DSet)X_field.getDomainSet()).getLengths();
277        mask_range = new float[1][len];
278        for (int t=0; t<len; t++) {
279          mask_range[0][t] = Float.NaN;
280        }
281        mask_range[0][0] = 0; //- field should not be all missing
282        mask_field.setSamples(mask_range, false);
283                                                                                                                                                  
284        try {
285          int binSize = ((lens[0]*lens[1]/(256*256))*4)/10;
286          if (binSize < 2) binSize = 2;
287          histoField = new HistogramField(X_field, Y_field, mask_field, 256, binSize);
288        } catch (Exception e) {
289          logger.error("Problem creating HistogramField", e);
290        }
291
292        Range rangeX = getImageRange(X_field);
293        Range rangeY = getImageRange(Y_field);
294        ColorTable clrTableX = getColorTable(X_field);
295        ColorTable clrTableY = getColorTable(Y_field);
296
297        dspMasterX = makeImageDisplay(getDataProjection(X_field), X_field, mask_field, 
298                         rangeX, clrTableX);
299
300        dspMasterY = makeImageDisplay(getDataProjection(Y_field), Y_field, mask_field, 
301                         rangeY, clrTableY);
302
303        dspMasterX.addDisplayListener(e -> {
304            double[] xProjection = dspMasterX.getProjectionMatrix();
305            double[] yProjection = dspMasterY.getProjectionMatrix();
306            if (xProjection.equals(yProjection))
307                return;
308
309            try {
310                dspMasterY.setProjectionMatrix(xProjection);
311            } catch (Exception ex) {
312                LogUtil.logException("dspMasterX.displayChanged", ex);
313            }
314        });
315
316        dspMasterY.addDisplayListener(e -> {
317            double[] xProjection = dspMasterX.getProjectionMatrix();
318            double[] yProjection = dspMasterY.getProjectionMatrix();
319            if (yProjection.equals(xProjection))
320                return;
321
322            try {
323                dspMasterX.setProjectionMatrix(yProjection);
324            } catch (Exception ex) {
325                LogUtil.logException("dspMasterX.displayChanged", ex);
326            }
327        });
328
329        X_name = ((((FunctionType)X_field.getType()).getFlatRange().getRealComponents())[0]).getName();
330        Y_name = ((((FunctionType)Y_field.getType()).getFlatRange().getRealComponents())[0]).getName();
331
332        if (statsTable != null) statsTable.setNames(X_name, Y_name);
333
334        Grid2DReadoutProbe probeX = new Grid2DReadoutProbe(X_field, dspMasterX);
335        Grid2DReadoutProbe probeY = new Grid2DReadoutProbe(Y_field, dspMasterY);
336        probeX.doMakeProbe(Color.red, dspMasterX);
337        probeY.doMakeProbe(Color.red, dspMasterY);
338        
339        ImageControl dCntrl = new ImageControl((HydraRGBDisplayable)dspMasterX.getDisplayables(0), getDisplayConventions());
340        ctw = new ColorTableWidget(dCntrl, ColorTableManager.getManager(), clrTableX, rangeX);
341        ctwCompX = ctw.getLegendPanel(BOTTOM_LEGEND);
342        dCntrl.ctw = ctw;
343
344        dCntrl = new ImageControl((HydraRGBDisplayable)dspMasterY.getDisplayables(0), getDisplayConventions());
345        ctw = new ColorTableWidget(dCntrl, ColorTableManager.getManager(), clrTableY, rangeY);
346        ctwCompY = ctw.getLegendPanel(BOTTOM_LEGEND);
347        dCntrl.ctw = ctw;
348
349        return true;
350    }
351
352    public void setup() throws VisADException, RemoteException {
353        dataSelectionX = getDataSelection();
354        dataChoiceX = getDataChoice();
355        X_data = dataChoiceX.getData(dataSelectionX);
356
357        if (X_data instanceof FlatField) {
358          X_field = (FlatField) X_data;
359        } else if (X_data instanceof FieldImpl) {
360          X_field = (FlatField) ((FieldImpl)X_data).getSample(0);
361        }
362
363        popupDataDialog("select Y Axis field", container, false, null);
364        
365        // if user canceled the popup, popupDataDialog will set the cancel flag
366        if (cancel) throw new VisADException("Scatter Display Canceled");
367
368        dataSelectionY = getDataSelection();
369        dataChoiceY = getDataChoice();
370
371        dataSelectionY.setGeoSelection(dataSelectionX.getGeoSelection());
372                                                                                                                                                  
373        Y_data = dataChoiceY.getData(dataSelectionY);
374
375        if (Y_data instanceof FlatField) {
376          Y_field = (FlatField) Y_data;
377        } else if (Y_data instanceof FieldImpl) {
378          Y_field = (FlatField) ((FieldImpl)Y_data).getSample(0);
379        }
380
381        if (!( X_field.getDomainSet().equals(Y_field.getDomainSet())))
382        {
383          Y_field = resample(X_field, Y_field);
384        }
385
386        Area_field = JPythonMethods.createAreaField(X_field);
387        statsTable = new StatsTable();
388    }
389
390    public void setupFromUnpersistence() throws VisADException, RemoteException {
391        X_data = dataChoiceX.getData(dataSelectionX);
392        if (X_data instanceof FlatField) {
393          X_field = (FlatField) X_data;
394        } else if (X_data instanceof FieldImpl) {
395          X_field = (FlatField) ((FieldImpl)X_data).getSample(0);
396        }
397                                                                                                                                                  
398        Y_data = dataChoiceY.getData(dataSelectionY);
399        if (Y_data instanceof FlatField) {
400          Y_field = (FlatField) Y_data;
401        } else if (X_data instanceof FieldImpl) {
402          Y_field = (FlatField) ((FieldImpl)Y_data).getSample(0);
403        }
404    }
405
406    @Override public void initAfterUnPersistence(ControlContext vc,
407                                                 Hashtable properties,
408                                                 List preSelectedDataChoices) 
409    {
410        super.initAfterUnPersistence(vc, properties, preSelectedDataChoices);
411
412        Color fg;
413        Color bg;
414        float[][] bgPalette;
415        if (getBlackBackground()) {
416            fg = Color.white;
417            bg = Color.black;
418            bgPalette = markPaletteBlackBackground;
419        } else {
420            fg = Color.black;
421            bg = Color.white;
422            bgPalette = markPaletteWhiteBackground;
423        }
424        scatterMaster.setForeground(fg);
425        scatterMaster.setBackground(bg);
426        try {
427            scatterMarkDsp.setColorPalette(bgPalette);
428        } catch (Exception ex) {
429            logger.error("could not change color palette", ex);
430        }
431    }
432    
433    @Override protected void popupDataDialog(final String dialogMessage,
434                                   Component from, boolean multiples,
435                                   List categories) {
436
437        List choices = selectDataChoices(dialogMessage, from,
438                                       multiples, categories);
439        if ((choices == null) || (choices.size() == 0)) {
440            logger.debug("popupDataDialog, no data choice, user canceled");
441                cancel = true;
442            return;
443        }
444        List<DataChoice> tempList = new ArrayList<>(1);
445        // TODO(jon): why is choices.get(0) now an arraylist!?
446        DataChoice firstChoice;
447        if (choices.get(0) instanceof List) {
448            List tmp = (List)choices.get(0);
449            firstChoice = (DataChoice)tmp.get(0);
450        } else {
451            firstChoice = (DataChoice)choices.get(0);
452        }
453        tempList.add(firstChoice);
454        final List clonedList =
455            DataChoice.cloneDataChoices(tempList);
456        dataSelection = ((DataChoice) clonedList.get(0)).getDataSelection();
457        //- don't do this in a separate thread like the IDV does.
458        //- We want the dataChoice list updated before return.
459        try {
460          addNewData(clonedList);
461        } catch (Exception exc) {
462          logException("Selecting new data", exc);
463        }
464    }
465
466
467    @Override public void initDone() {
468       try {
469         DisplayMaster master = makeScatterDisplay();
470         for (int k=0; k<n_selectors; k++) {
471           scatterBoxSelectors.add(new ScatterBoxSelector(master, selectorColors[k], (float)k));
472           scatterCurveSelectors.add(new ScatterCurveSelector(master, selectorColors[k], (float)k));
473         }
474         master.draw();
475
476         for (int k=0; k<n_selectors; k++) {
477           SubsetRubberBandBox X_subsetBox =
478              new SubsetRubberBandBox(getIsLatLon(X_field), X_field,
479                      ((MapProjectionDisplayJ3D)dspMasterX).getDisplayCoordinateSystem(), 1, false);
480           X_subsetBox.setColor(selectorColors[k]);
481
482           ImageBoxSelector markX = new ImageBoxSelector(X_subsetBox, X_field.getDomainSet(), dspMasterX, selectorColors[k], (float)k+1, statsTable);
483
484           SubsetRubberBandBox Y_subsetBox =
485              new SubsetRubberBandBox(getIsLatLon(Y_field), Y_field,
486                 ((MapProjectionDisplayJ3D)dspMasterY).getDisplayCoordinateSystem(), 1, false);
487           Y_subsetBox.setColor(selectorColors[k]);
488           ImageBoxSelector markY = new ImageBoxSelector(Y_subsetBox, Y_field.getDomainSet(), dspMasterY, selectorColors[k], (float)k+1, statsTable);
489
490           markX.setOther(markY);
491           markY.setOther(markX);
492           imageXBoxSelectors.add(markX);
493           imageYBoxSelectors.add(markY);
494         }
495
496         for (int k=0; k<n_selectors; k++) {
497           CurveDrawer curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
498           curveDraw.setColor(selectorColors[k]);
499           curveDraw.setLineWidth(2);
500           ImageCurveSelector curveX = new ImageCurveSelector(curveDraw, X_field, dspMasterX, selectorColors[k], (float) k+1, statsTable);
501           curveX.setActive(false);
502           curveDraw.addAction(curveX);
503           curveX.setVisible(false);
504           dspMasterX.addDisplayable(curveDraw);
505
506           curveDraw = new CurveDrawer(RealType.Longitude, RealType.Latitude, 1);
507           curveDraw.setColor(selectorColors[k]);
508           curveDraw.setLineWidth(2);
509           ImageCurveSelector curveY = new ImageCurveSelector(curveDraw, Y_field, dspMasterY, selectorColors[k], (float) k+1, statsTable);
510           curveY.setActive(false);
511           curveDraw.addAction(curveY);
512           curveY.setVisible(false);
513           dspMasterY.addDisplayable(curveDraw);
514
515           curveX.setOther(curveY);
516           curveY.setOther(curveX);
517           imageXCurveSelectors.add(curveX);
518           imageYCurveSelectors.add(curveY);
519         }
520
521         for (int k=0; k<n_selectors; k++) {
522           JToggleButton jtog = selectorToggleButtons[k];
523          
524           jtog.addActionListener(e -> {
525              int idx = Integer.valueOf(e.getActionCommand());
526              try {
527                for (int i=0; i<n_selectors; i++) {
528                  ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(i);
529                  ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(i);
530                  ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(i);
531                  ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(i);
532                  ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(i);
533                  ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(i);
534
535                  if (i == idx) {
536                    if (!selectorToggleButtons[i].isSelected()) {
537
538                      if (statsTable != null) statsTable.resetValues(i);
539
540                      boxSel.reset();
541                      boxSel.setActive(false);
542                      boxSel.setVisible(false);
543
544                      imageXbox.reset();
545                      imageXbox.setActive(false);
546                      imageXbox.setVisible(false);
547
548                      imageYbox.reset();
549                      imageYbox.setActive(false);
550                      imageYbox.setVisible(false);
551
552                      curveSel.reset();
553                      curveSel.setActive(false);
554                      curveSel.setVisible(false);
555
556                      imageXcurve.reset();
557                      imageXcurve.setActive(false);
558                      imageXcurve.setVisible(false);
559                      imageYcurve.reset();
560                      imageYcurve.setActive(false);
561                      imageYcurve.setVisible(false);
562                      selectorToggleButtons[i].setSelected(true);
563                    }
564                    boxSel.setActive(!getSelectByCurve());
565                    boxSel.setVisible(!getSelectByCurve());
566                    imageXbox.setActive(!getSelectByCurve());
567                    imageXbox.setVisible(!getSelectByCurve());
568                    imageYbox.setActive(!getSelectByCurve());
569                    imageYbox.setVisible(!getSelectByCurve());
570
571                    curveSel.setActive(getSelectByCurve());
572                    curveSel.setVisible(getSelectByCurve());
573                    imageXcurve.setActive(getSelectByCurve());
574                    imageXcurve.setVisible(getSelectByCurve());
575                    imageYcurve.setActive(getSelectByCurve());
576                    imageYcurve.setVisible(getSelectByCurve());
577                  }
578                  else {
579                    selectorToggleButtons[i].setSelected(false);
580                    boxSel.setActive(false);
581                    boxSel.setVisible(false);
582                    imageXbox.setActive(false);
583                    imageXbox.setVisible(false);
584                    imageYbox.setActive(false);
585                    imageYbox.setVisible(false);
586                    curveSel.setActive(false);
587                    curveSel.setVisible(false);
588                    imageXcurve.setActive(false);
589                    imageXcurve.setVisible(false);
590                    imageYcurve.setActive(false);
591                    imageYcurve.setVisible(false);
592                  }
593                }
594              } catch (Exception exc) {
595                logger.error("Problem handling toggle", exc);
596              }
597           });
598
599           ScatterBoxSelector boxSel = (ScatterBoxSelector) scatterBoxSelectors.get(k);
600           ImageBoxSelector imageXbox = (ImageBoxSelector) imageXBoxSelectors.get(k);
601           ImageBoxSelector imageYbox = (ImageBoxSelector) imageYBoxSelectors.get(k);
602           ScatterCurveSelector curveSel = (ScatterCurveSelector) scatterCurveSelectors.get(k);
603           ImageCurveSelector imageXcurve = (ImageCurveSelector) imageXCurveSelectors.get(k);
604           ImageCurveSelector imageYcurve = (ImageCurveSelector) imageYCurveSelectors.get(k);
605
606           if (k == 0) {
607              jtog.setSelected(true);
608              boxSel.setActive(!getSelectByCurve());
609              boxSel.setVisible(!getSelectByCurve());
610              imageXbox.setActive(!getSelectByCurve());
611              imageXbox.setVisible(!getSelectByCurve());
612              imageYbox.setActive(!getSelectByCurve());
613              imageYbox.setVisible(!getSelectByCurve());
614
615              curveSel.setActive(getSelectByCurve());
616              curveSel.setVisible(getSelectByCurve());
617              imageXcurve.setActive(getSelectByCurve());
618              imageXcurve.setVisible(getSelectByCurve());
619              imageYcurve.setActive(getSelectByCurve());
620              imageYcurve.setVisible(getSelectByCurve());
621            }
622            else {
623              boxSel.setActive(false);
624              boxSel.setVisible(false);
625              imageXbox.setActive(false);
626              imageXbox.setVisible(false);
627              imageYbox.setActive(false);
628              imageYbox.setVisible(false);
629              curveSel.setActive(false);
630              curveSel.setVisible(false);
631              imageXcurve.setActive(false);
632              imageXcurve.setVisible(false);
633              imageYcurve.setActive(false);
634              imageYcurve.setVisible(false);
635           }
636         }
637       } catch (Exception e) {
638         logger.error("Problem initializing", e);
639       }
640    }
641    
642    public DisplayMaster makeScatterDisplay() throws VisADException, RemoteException {
643
644       ScatterDisplayable scatterDsp = new ScatterDisplayable("scatter",
645                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
646       float[] valsX = X_field.getFloats(false)[0];
647       float[] valsY = Y_field.getFloats(false)[0];
648       Integer1DSet set = new Integer1DSet(valsX.length);
649       FlatField scatter = new FlatField(
650           new FunctionType(RealType.Generic,
651               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
652       float[] mask = new float[valsX.length];
653       for (int k=0; k<mask.length; k++) {
654           mask[k] = 0;
655       }
656       scatterFieldRange = new float[][] {valsX, valsY, mask};
657       scatter.setSamples(scatterFieldRange);
658       scatterDsp.setPointSize(2f);
659       scatterDsp.setRangeForColor(0,n_selectors);
660
661       float[] xRange = minmax(valsX);
662       float[] yRange = minmax(valsY);
663       
664       scatterDsp.setData(scatter);
665
666       scatterMarkDsp = new ScatterDisplayable("scatter",
667                   RealType.getRealType("mask"), markPaletteBlackBackground, false);
668       set = new Integer1DSet(2);
669       scatter = new FlatField(
670           new FunctionType(RealType.Generic,
671               new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), set);
672       scatterMarkDsp.setData(scatter);
673       scatterMarkDsp.setPointSize(2f);
674       scatterMarkDsp.setRangeForColor(0,n_selectors);
675
676       DisplayMaster master = scatterMaster;
677       ((XYDisplay)master).showAxisScales(true);
678       AxisScale scaleX = ((XYDisplay)master).getXAxisScale();
679       scaleX.setTitle(X_name);
680       AxisScale scaleY = ((XYDisplay)master).getYAxisScale();
681       scaleY.setTitle(Y_name);
682
683       ((XYDisplay)master).setXRange((double)xRange[0], (double)xRange[1]);
684       ((XYDisplay)master).setYRange((double)yRange[0], (double)yRange[1]);
685       master.addDisplayable(scatterDsp);
686       master.addDisplayable(scatterMarkDsp);
687
688       return master;
689    }
690
691    @Override public Container doMakeContents() {
692        JPanel pane = new JPanel(new GridLayout(1,3));
693
694        Component[] comps = new Component[] {null, null, null};
695        comps[0] = dspMasterX.getComponent();
696        comps[1] = dspMasterY.getComponent();
697        comps[2] = getScatterTabComponent();
698
699        JPanel panelX = new JPanel(new BorderLayout());
700        panelX.setBorder(new EmptyBorder(4,4,4,4));
701        panelX.add(comps[0], BorderLayout.CENTER);
702        panelX.add(ctwCompX, BorderLayout.SOUTH);
703
704        JPanel panelY = new JPanel(new BorderLayout());
705        panelY.setBorder(new EmptyBorder(4,4,4,4));
706        panelY.add(comps[1], BorderLayout.CENTER);
707        panelY.add(ctwCompY, BorderLayout.SOUTH);
708
709        JPanel panelS = new JPanel(new BorderLayout());
710        panelS.setBorder(new EmptyBorder(4,4,4,4));
711        panelS.add(comps[2], BorderLayout.CENTER);
712
713        pane.add(panelX);
714        pane.add(panelY);
715        pane.add(panelS);
716
717        
718        JPanel buttonPanel = new JPanel();
719        buttonPanel.setLayout(new FlowLayout());
720        JRadioButton boxSelect = new JRadioButton("Box");
721        boxSelect.setSelected(true);
722        JRadioButton curveSelect = new JRadioButton("Curve");
723        ButtonGroup buttonGroup = new ButtonGroup();
724        buttonGroup.add(boxSelect);
725        buttonGroup.add(curveSelect);
726        buttonPanel.add(boxSelect);
727        buttonPanel.add(curveSelect);
728
729        boxCurveSwitch = new BoxCurveSwitch();
730        boxSelect.addActionListener(boxCurveSwitch);
731        curveSelect.addActionListener(boxCurveSwitch);
732        
733
734        JPanel toggleButtonPanel = new JPanel(new FlowLayout());
735        for (int k=0; k<n_selectors; k++) {
736          JToggleButton jtog = 
737             new JToggleButton(
738                 new ImageIcon(getClass().getResource("/edu/wisc/ssec/mcidasv/resources/icons/buttons/subset12.jpg")));
739          jtog.setBorder(new CompoundBorder(new LineBorder(selectorColors[k],2), new EmptyBorder(4,4,4,4)));
740          jtog.setActionCommand(String.valueOf(k));
741          toggleButtonPanel.add(jtog);
742          selectorToggleButtons[k] = jtog;
743        }
744
745        buttonPanel.add(toggleButtonPanel);
746
747        JButton computeStatsButton = new JButton("Compute Statistics");
748
749        computeStatsButton.addActionListener(e -> {
750           if (statsTable == null) {
751             statsTable = new StatsTable();
752           }
753
754           statsTable.setIsShowing();
755           statsTable.setFields(X_field, Y_field,0);
756        });
757
758        JButton reset = new JButton("Reset Selections");
759        reset.addActionListener(e -> resetAllScatterSelectors());
760
761        buttonPanel.add(reset);
762        buttonPanel.add(computeStatsButton);
763        buttonPanel.add(new JLabel("Background Color:"));
764        buttonPanel.add(bgColorBlack);
765        buttonPanel.add(bgColorWhite);
766
767        //-container = pane;
768        JPanel new_pane = new JPanel(new BorderLayout());
769        new_pane.add(pane, BorderLayout.CENTER);
770        new_pane.add(buttonPanel, BorderLayout.SOUTH);
771        container = new_pane;
772        return container;
773    }
774
775    /**
776     * Reset the state of all scatter display selections.
777     */
778    public void resetAllScatterSelectors() {
779        for (int i = 0; i < n_selectors; i++) {
780            resetScatterSelector(i);
781        }
782    }
783
784    /**
785     * Reset the state of a specific scatter display selection.
786     *
787     * @param idx Index of the scatter display selection within {@link #n_selectors}.
788     */
789    public void resetScatterSelector(int idx) {
790        try {
791            if (statsTable != null) {
792                statsTable.resetValues(idx);
793            }
794
795            ScatterBoxSelector boxSel = scatterBoxSelectors.get(idx);
796            ImageBoxSelector imageXbox = imageXBoxSelectors.get(idx);
797            ImageBoxSelector imageYbox = imageYBoxSelectors.get(idx);
798            ScatterCurveSelector curveSel = scatterCurveSelectors.get(idx);
799            ImageCurveSelector imageXcurve = imageXCurveSelectors.get(idx);
800            ImageCurveSelector imageYcurve = imageYCurveSelectors.get(idx);
801
802            boxSel.reset();
803            boxSel.setActive(false);
804            boxSel.setVisible(false);
805
806            imageXbox.reset();
807            imageXbox.setActive(false);
808            imageXbox.setVisible(false);
809
810            imageYbox.reset();
811            imageYbox.setActive(false);
812            imageYbox.setVisible(false);
813
814            curveSel.reset();
815            curveSel.setActive(false);
816            curveSel.setVisible(false);
817
818            imageXcurve.reset();
819            imageXcurve.setActive(false);
820            imageXcurve.setVisible(false);
821            imageYcurve.reset();
822            imageYcurve.setActive(false);
823            imageYcurve.setVisible(false);
824        } catch (Exception e) {
825            logger.error("Problem resetting selector at index " + idx, e);
826        }
827    }
828
829    public void setBlackBackground(boolean value) {
830        blackBackground = value;
831    }
832    
833    public boolean getBlackBackground() {
834        return blackBackground;
835    }
836
837    protected Component getScatterTabComponent() {
838       try {
839         scatterMaster = new XYDisplay("Scatter", RealType.XAxis, RealType.YAxis);
840       } catch (Exception e) {
841         logger.error("Problem creating XYDisplay", e);
842       }
843       return scatterMaster.getComponent();
844    }
845
846    public DisplayMaster makeImageDisplay(MapProjection mapProj, FlatField image, 
847                  FlatField mask_image, Range imageRange, ColorTable colorTable) 
848           throws VisADException, RemoteException {
849      MapProjectionDisplayJ3D mapProjDsp;
850      DisplayMaster dspMaster;
851
852      mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
853      mapProjDsp.enableRubberBanding(false);
854      dspMaster = mapProjDsp;
855      mapProjDsp.setMapProjection(mapProj);
856
857      RealType imageRangeType =
858        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
859
860      boolean alphaflag = false;
861      HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, alphaflag, null);
862
863      imageDsp.setData(image);
864      dspMaster.addDisplayable(imageDsp);
865      addMapDisplayables(mapProjDsp);
866
867      if (mask_image != null) {
868        RGBDisplayable maskDsp = 
869            new ScatterDisplayable("mask", RealType.Generic, maskColorPalette, false);
870        maskDsp.setData(mask_image);
871        maskDsp.setRangeForColor(0, n_selectors-1);
872        dspMaster.addDisplayable(maskDsp);
873      }
874
875      dspMaster.draw();
876
877      ScalarMap colorMap = imageDsp.getColorMap();
878      colorMap.setRange(imageRange.getMin(), imageRange.getMax());
879      BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
880      float[][] ct = colorTable.getColorTable();
881
882      if ( !(alphaflag) && (ct.length == 4) ) {
883         float[][] new_ct = new float[3][];
884         new_ct[0] = ct[0];
885         new_ct[1] = ct[1];
886         new_ct[2] = ct[2];
887         ct = new_ct;
888      }
889
890      clrCntrl.setTable(ct);
891
892      return dspMaster;
893    }
894
895    public Range getImageRange(FlatField image)
896           throws VisADException, RemoteException {
897      DisplayConventions dc = getDisplayConventions();
898      Range[] range = GridUtil.fieldMinMax(image);
899      Range imageRange = range[0];
900      RealType imageRangeType =
901        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
902      String canonicalName = DataAlias.aliasToCanonical(imageRangeType.getName());
903      Range dfltRange = dc.getParamRange(canonicalName, null);
904
905      if (dfltRange == null) {
906        imageRange = range[0];
907      }
908      else if ((imageRange.getMax() - imageRange.getMin()) < (dfltRange.getMax() - dfltRange.getMin())) {
909      }
910      else {
911        imageRange = dfltRange;
912      }
913      return imageRange;
914    }
915
916    public ColorTable getColorTable(FlatField image) 
917           throws VisADException, RemoteException {
918      RealType imageRangeType =
919        (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
920      DisplayConventions dc = getDisplayConventions();
921      return dc.getParamColorTable(imageRangeType.getName());
922    }
923
924
925    public MapProjection getDataProjection(FlatField image) 
926           throws VisADException, RemoteException {
927      MapProjection mp = null;
928      //- get MapProjection from incoming image.  If none, use default method
929      FunctionType fnc_type = (FunctionType) image.getType();
930      RealTupleType rtt = fnc_type.getDomain();
931      CoordinateSystem cs = rtt.getCoordinateSystem();
932      Set domainSet = image.getDomainSet();
933
934      if (cs instanceof visad.CachingCoordinateSystem) {
935        cs = ((visad.CachingCoordinateSystem)cs).getCachedCoordinateSystem();
936      }
937
938      if (cs instanceof MapProjection) {
939        return (MapProjection) cs;
940      }
941      else if (cs instanceof LongitudeLatitudeCoordinateSystem) {
942        Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
943        try {
944          mp = new LambertAEA(rect);
945        } catch (Exception e) {
946          System.out.println(" getDataProjection"+e);
947        }
948        return mp;
949      }
950
951      float minLon = Float.NaN;
952      float minLat = Float.NaN;
953      float delLon = Float.NaN;
954      float delLat = Float.NaN;
955
956      if (domainSet instanceof LinearLatLonSet) {
957         MathType type0 = ((SetType)domainSet.getType()).getDomain().getComponent(0);
958         int latI = RealType.Latitude.equals(type0) ? 0 : 1;
959         int lonI = (latI == 1) ? 0 : 1;
960
961         float[] min = ((LinearLatLonSet)domainSet).getLow();
962         float[] max = ((LinearLatLonSet)domainSet).getHi();
963         minLon = min[lonI];
964         minLat = min[latI];
965         delLon = max[lonI] - min[lonI];
966         delLat = max[latI] - min[latI];
967
968         try {
969            mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
970                    new Rectangle2D.Float(minLon, minLat, delLon, delLat));
971         } catch (Exception e) {
972             logException("MultiSpectralControl.getDataProjection", e);
973         }
974
975         return mp;
976      }
977      else if (domainSet instanceof Gridded2DSet) {
978        rtt = ((SetType)domainSet.getType()).getDomain();
979        rtt = RealTupleType.SpatialEarth2DTuple;
980        if (!(rtt.equals(RealTupleType.SpatialEarth2DTuple) || rtt.equals(RealTupleType.LatitudeLongitudeTuple))) {
981          minLon = -180f;
982          minLat = -90f;
983          delLon = 360f;
984          delLat = 180f;
985        }
986        else {
987          int latI = rtt.equals(RealTupleType.SpatialEarth2DTuple) ? 1 : 0;
988          int lonI = (latI == 1) ? 0 : 1;
989
990          float[] min = ((Gridded2DSet)domainSet).getLow();
991          float[] max = ((Gridded2DSet)domainSet).getHi();
992          minLon = min[lonI];
993          minLat = min[latI];
994          delLon = max[lonI] - min[lonI];
995          delLat = max[latI] - min[latI];
996        }
997      }
998      
999      try {
1000         mp = new TrivialMapProjection(RealTupleType.SpatialEarth2DTuple,
1001                 new Rectangle2D.Float(minLon, minLat, delLon, delLat));
1002      } catch (Exception e) {
1003          logException("MultiSpectralControl.getDataProjection", e);
1004      }
1005
1006      return mp;
1007    }
1008
1009    public void addMapDisplayables(MapProjectionDisplayJ3D mapProjDsp) 
1010           throws VisADException, RemoteException {
1011        MapLines mapLines  = new MapLines("maplines");
1012        URL      mapSource =
1013        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
1014        try {
1015            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1016            mapLines.setMapLines(mapAdapter.getData());
1017            mapLines.setColor(java.awt.Color.cyan);
1018            mapProjDsp.addDisplayable(mapLines);
1019        } catch (Exception excp) {
1020            System.out.println("Can't open map file " + mapSource);
1021            System.out.println(excp);
1022        }
1023                                                                                                                                                  
1024        mapLines  = new MapLines("maplines");
1025        mapSource =
1026        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
1027        try {
1028            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1029            mapLines.setMapLines(mapAdapter.getData());
1030            mapLines.setColor(java.awt.Color.cyan);
1031            mapProjDsp.addDisplayable(mapLines);
1032        } catch (Exception excp) {
1033            System.out.println("Can't open map file " + mapSource);
1034            System.out.println(excp);
1035        }
1036                                                                                                                                                  
1037        mapLines  = new MapLines("maplines");
1038        mapSource =
1039        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
1040        try {
1041            BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
1042            mapLines.setMapLines(mapAdapter.getData());
1043            mapLines.setColor(java.awt.Color.cyan);
1044            mapProjDsp.addDisplayable(mapLines);
1045        } catch (Exception excp) {
1046            System.out.println("Can't open map file " + mapSource);
1047            System.out.println(excp);
1048        }
1049    }
1050
1051    public boolean getSelectByCurve() {
1052      return selectByCurve;
1053    }
1054
1055    private FlatField resample(FlatField X_field, FlatField Y_field) throws VisADException, RemoteException {
1056
1057       RealTupleType X_domainRef = null;
1058       RealTupleType Y_domainRef = null;
1059       float[][] coords = null;
1060       int[] indexes = null;
1061       float[][] Yvalues = Y_field.getFloats(false);
1062       float[][] Xsamples = ((SampledSet)X_field.getDomainSet()).getSamples(false);
1063
1064       CoordinateSystem X_cs = X_field.getDomainCoordinateSystem();
1065       if (X_cs == null) {
1066          RealTupleType X_domain = ((FunctionType)X_field.getType()).getDomain();
1067       }
1068       else {
1069         X_domainRef = X_cs.getReference();
1070       }
1071
1072       CoordinateSystem Y_cs = Y_field.getDomainCoordinateSystem();
1073       if (Y_cs == null) {
1074          RealTupleType Y_domain = ((FunctionType)Y_field.getType()).getDomain();
1075       }
1076       else {
1077         Y_domainRef = Y_cs.getReference();
1078       }
1079
1080       if ( X_domainRef != null && Y_domainRef != null) {
1081         Xsamples = X_cs.toReference(Xsamples);
1082         coords = Y_cs.fromReference(Xsamples);
1083         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(coords);
1084       }
1085       else if ( X_domainRef == null && Y_domainRef != null ) {
1086         Xsamples = Y_cs.fromReference(Xsamples);
1087         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1088       }
1089       else if ( X_domainRef != null && Y_domainRef == null) {
1090         Xsamples = X_cs.toReference(Xsamples);
1091         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1092
1093         // TODO this is a hack for the longitude range problem
1094         float[] hi = domSet.getHi();
1095         if (hi[0] <= 180f) {
1096           for (int t=0; t<Xsamples[0].length; t++) {
1097             if (Xsamples[0][t] > 180f) Xsamples[0][t] -=360;
1098           }
1099         }
1100         
1101         indexes = ((SampledSet)Y_field.getDomainSet()).valueToIndex(Xsamples);
1102       }
1103       else if (X_domainRef == null && Y_domainRef == null) {
1104         Gridded2DSet domSet = (Gridded2DSet) Y_field.getDomainSet();
1105         indexes = domSet.valueToIndex(Xsamples);
1106       }
1107       
1108       float[][] new_values = new float[1][indexes.length];
1109       for (int k=0; k<indexes.length; k++) {
1110          new_values[0][k] = Float.NaN;
1111          if (indexes[k] >= 0) {
1112            new_values[0][k] = Yvalues[0][indexes[k]];
1113          }
1114       }
1115
1116       FunctionType ftype = new FunctionType(((FunctionType)X_field.getType()).getDomain(),
1117                ((FunctionType)Y_field.getType()).getRange());
1118       Y_field = new FlatField(ftype, X_field.getDomainSet());
1119       Y_field.setSamples(new_values);
1120
1121       return Y_field;
1122    }
1123
1124
1125    private class ScatterDisplayable extends RGBDisplayable {
1126       ScatterDisplayable(String name, RealType rgbRealType, float[][] colorPalette, boolean alphaflag) 
1127           throws VisADException, RemoteException {
1128         super(name, rgbRealType, colorPalette, alphaflag);
1129       }
1130    }
1131
1132    private class ImageControl extends DisplayControlImpl {
1133      HydraRGBDisplayable rgbDisp;
1134      DisplayConventions dc;
1135      ColorTableWidget ctw;
1136
1137      ImageControl(HydraRGBDisplayable rgbDisp, DisplayConventions dc) {
1138        super();
1139        this.rgbDisp = rgbDisp;
1140        this.dc = dc;
1141      }
1142
1143      @Override public void setRange(Range r) throws VisADException, RemoteException {
1144          if (r != null) {
1145              rgbDisp.setRangeForColor(r.getMin(), r.getMax());
1146          }
1147      }
1148
1149      @Override public DisplayConventions getDisplayConventions() {
1150        return dc;
1151      }
1152
1153      @Override public void setColorTable(ColorTable ct) {
1154        try {
1155          ctw.setColorTable(ct);
1156          ScalarMap colorMap = rgbDisp.getColorMap();
1157          BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
1158
1159          // Force incoming color dimension to that of the colorMap
1160          //
1161          int numComps = clrCntrl.getNumberOfComponents();
1162          float[][] clrTable = ct.getColorTable();
1163          float[][] newTable = null;
1164          if (numComps != clrTable.length) {
1165            if (numComps < clrTable.length) {
1166              newTable = new float[numComps][clrTable[0].length];
1167              for (int k=0; k<numComps; k++) {
1168                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1169              }
1170            }
1171            else if (numComps > clrTable.length) {
1172              newTable = new float[numComps][clrTable[0].length];
1173              for (int k=0; k<clrTable.length; k++) {
1174                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1175              }
1176              newTable[3] = new float[clrTable[0].length];
1177            }
1178          } else {
1179              newTable = new float[numComps][clrTable[0].length];
1180              for (int k = 0; k < clrTable.length; k++) {
1181                System.arraycopy(clrTable[k], 0, newTable[k], 0, newTable[0].length);
1182              }
1183          } 
1184          clrCntrl.setTable(newTable);
1185        } 
1186        catch (Exception e) {
1187          LogUtil.logException("Problem changing color table", e);
1188        }
1189      }
1190    }
1191
1192    private class ImageCurveSelector extends CellImpl implements DisplayListener {
1193      boolean init = false;
1194      CurveDrawer curveDraw;
1195      DisplayMaster dspMaster;
1196      Gridded2DSet domainSet;
1197      CoordinateSystem cs;
1198      int domainLen_0;
1199      int domainLen_1;
1200      ImageCurveSelector other;
1201      UnionSet last_uSet = null;
1202      boolean imageLatLon = false;
1203      boolean active = true;
1204      float maskVal;
1205      LineDrawing lastCurve;
1206      StatsTable myTable = null;
1207      int myTableIndex = 0;
1208
1209      ImageCurveSelector(CurveDrawer curveDraw, FlatField image, DisplayMaster master, Color color, float maskVal, StatsTable mst) 
1210           throws VisADException, RemoteException {
1211        this.curveDraw = curveDraw;
1212        this.maskVal = maskVal;
1213        this.myTable = mst;
1214        myTableIndex = 0;
1215        if (color == Color.magenta) myTableIndex = 1;
1216        if (color == Color.green) myTableIndex = 2;
1217        if (color == Color.blue) myTableIndex = 3;
1218        dspMaster = master;
1219        dspMaster.addDisplayListener(this);
1220        domainSet = (Gridded2DSet) image.getDomainSet();
1221        int[] lens = domainSet.getLengths();
1222        domainLen_0 = lens[0];
1223        domainLen_1 = lens[1];
1224        cs = ((FunctionType)image.getType()).getDomain().getCoordinateSystem();
1225        RealTupleType reference = null;
1226        if (cs != null) {
1227          reference = cs.getReference();
1228        }
1229        else {
1230          reference = ((SetType)domainSet.getType()).getDomain();
1231        }
1232        RealType[] rtypes = reference.getRealComponents();
1233        if (rtypes[0].equals(RealType.Latitude)) imageLatLon = true;
1234        lastCurve = new LineDrawing("lastCurve");
1235        lastCurve.setColor(color);
1236        lastCurve.setLineWidth(2);
1237        master.addDisplayable(lastCurve);
1238      }
1239
1240      @Override public void displayChanged(DisplayEvent de)
1241             throws VisADException, RemoteException {
1242         if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1243           UnionSet uSet = curveDraw.getCurves();
1244           if (uSet == last_uSet) return;
1245           SampledSet[] sets = uSet.getSets();
1246           int s_idx = sets.length-1;
1247           float[][] crv;
1248
1249           if (cs != null) {
1250             crv = sets[s_idx].getSamples();
1251             if (imageLatLon) {
1252                float[] tmp = crv[0];
1253                crv[0] = crv[1];
1254                crv[1] = tmp;
1255             }
1256             crv = cs.fromReference(crv);
1257             crv = domainSet.valueToGrid(crv);
1258           }
1259           else {
1260             crv = sets[s_idx].getSamples();
1261             crv = domainSet.valueToGrid(crv);
1262           }
1263
1264           float[][] onImage = new float[2][crv[0].length];
1265           int cnt = 0;
1266           for (int i=0; i<crv[0].length; i++) {
1267             if ( ((crv[0][i] >= 0)&&(crv[0][i] <= domainLen_0)) &&
1268                  ((crv[1][i] >= 0)&&(crv[1][i] <= domainLen_1)) ) {
1269               onImage[0][cnt] = crv[0][i];
1270               onImage[1][cnt] = crv[1][i];
1271               cnt++;
1272             }
1273           }
1274           uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1275           last_uSet = uSet;
1276           lastCurve.setData(last_uSet);
1277           curveDraw.setCurves(uSet);
1278           other.updateCurve(sets[s_idx]);
1279
1280           if (cnt == 0) {
1281             return;
1282           }
1283
1284           float[][] tmp = new float[2][cnt];
1285           System.arraycopy(onImage[0], 0, tmp[0], 0, cnt);
1286           System.arraycopy(onImage[1], 0, tmp[1], 0, cnt);
1287           onImage = tmp;
1288
1289           float[] minmaxvals = minmax(onImage[0]);
1290           int low_0 = Math.round(minmaxvals[0]);
1291           int hi_0 = Math.round(minmaxvals[1]);
1292           minmaxvals = minmax(onImage[1]);
1293           int low_1 = Math.round(minmaxvals[0]);
1294           int hi_1 = Math.round(minmaxvals[1]);
1295
1296           int len_0 = (hi_0 - low_0) + 1;
1297           int len_1 = (hi_1 - low_1) + 1;
1298           int len = len_0*len_1;
1299
1300           tmp = new float[3][len];
1301           int[] tmpsel = new int[len];
1302
1303           int num_inside = 0;
1304           for (int j=0; j<len_1; j++) {
1305             for (int i=0; i<len_0; i++) {
1306               int idx = (j+low_1)*domainLen_0 + (i+low_0);
1307               float x = (float) (i + low_0);
1308               float y = (float) (j + low_1);
1309               if (DelaunayCustom.inside(crv, x, y)) {
1310                 tmp[0][num_inside] = scatterFieldRange[0][idx];
1311                 tmp[1][num_inside] = scatterFieldRange[1][idx];
1312                 tmp[2][num_inside] = maskVal;
1313                 tmpsel[num_inside] = idx;
1314                 num_inside++;
1315               }
1316             }
1317           }
1318           len = num_inside;
1319           float[][] markScatter = new float[3][len];
1320           System.arraycopy(tmp[0], 0, markScatter[0], 0, len);
1321           System.arraycopy(tmp[1], 0, markScatter[1], 0, len);
1322           System.arraycopy(tmp[2], 0, markScatter[2], 0, len);
1323
1324
1325           int last_len = 0;
1326           float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1327           tmp = new float[3][lastMark[0].length];
1328           for (int k=0; k<lastMark[0].length; k++) {
1329             if (lastMark[2][k] != maskVal) {
1330               tmp[0][last_len] = lastMark[0][k];
1331               tmp[1][last_len] = lastMark[1][k];
1332               tmp[2][last_len] = lastMark[2][k];
1333               last_len++;
1334             }
1335           }
1336
1337           float[][] newMarkScatter = new float[3][len+last_len];
1338           System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1339           System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1340           System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1341           System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1342           System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1343           System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1344
1345           Integer1DSet dset = new Integer1DSet(len+last_len);
1346           FlatField scatterFieldMark = new FlatField(
1347             new FunctionType(RealType.Generic,
1348                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1349
1350           scatterFieldMark.setSamples(newMarkScatter, false);
1351           scatterMarkDsp.setData(scatterFieldMark);
1352
1353           if (myTable != null) {
1354             int[] selected = new int[len];
1355             System.arraycopy(tmpsel, 0, selected, 0, len);
1356             total_area = JPythonMethods.computeSum(Area_field, selected);
1357             myTable.setPoints(markScatter, len, myTableIndex, total_area);  
1358           }
1359
1360         }
1361      }
1362
1363      public void setActive(boolean active) {
1364        this.active = active;
1365      }
1366
1367      public void reset() throws VisADException, RemoteException {
1368
1369        float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1370        float[][] tmp = new float[3][lastMark[0].length];
1371        int cnt = 0;
1372        for (int k=0; k<lastMark[0].length; k++) {
1373          if (lastMark[2][k] != maskVal) {
1374             tmp[0][cnt] = lastMark[0][k];
1375             tmp[1][cnt] = lastMark[1][k];
1376             tmp[2][cnt] = lastMark[2][k];
1377             cnt++;
1378          }
1379        }
1380
1381        RealTupleType type = ((SetType)curveDraw.getCurves().getType()).getDomain();
1382        curveDraw.setCurves(new UnionSet(new Gridded2DSet[]{
1383            new Gridded2DSet(type, new float[][] {
1384            { 0.0f }, { 0.0f }}, 1) }));
1385        
1386        lastCurve.setData(new UnionSet(new Gridded2DSet[]{
1387            new Gridded2DSet(type, new float[][] {
1388            { 0.0f }, { 0.0f }}, 1) }));
1389
1390        FlatField scatterFieldMark = null;
1391        if (cnt == 0) {
1392        Integer1DSet dset = new Integer1DSet(2);
1393        scatterFieldMark = new FlatField(
1394        new FunctionType(RealType.Generic,
1395              new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1396        float[][] markScatter = new float[3][2]; 
1397        for (int k=0; k<2; k++) {
1398          markScatter[0][k] = scatterFieldRange[0][k];
1399          markScatter[1][k] = scatterFieldRange[1][k];
1400          markScatter[2][k] = 0;
1401        }
1402        scatterFieldMark.setSamples(markScatter, false);
1403        }
1404        else {
1405          Integer1DSet dset = new Integer1DSet(cnt);
1406          scatterFieldMark = new FlatField(
1407          new FunctionType(RealType.Generic,
1408                new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1409          float[][] markScatter = new float[3][cnt];
1410          for (int k=0; k<cnt; k++) {
1411            markScatter[0][k] = tmp[0][k];
1412            markScatter[1][k] = tmp[1][k];
1413            markScatter[2][k] = tmp[2][k];
1414          }
1415          scatterFieldMark.setSamples(markScatter, false);
1416        }
1417
1418        scatterMarkDsp.setData(scatterFieldMark);
1419      }
1420
1421      public void updateCurve(SampledSet set) throws VisADException, RemoteException {
1422        last_uSet = new UnionSet(new SampledSet[] {set});
1423        curveDraw.setCurves(last_uSet);
1424        lastCurve.setData(last_uSet);
1425      }
1426
1427      public void setOther(ImageCurveSelector other) {
1428        this.other = other;
1429      }
1430
1431      @Override public void doAction()
1432           throws VisADException, RemoteException {
1433        if (!init) {
1434          init = true;
1435          return;
1436        }
1437      }
1438
1439      public void setVisible(boolean visible) throws VisADException, RemoteException {
1440        curveDraw.setVisible(visible);
1441      }
1442
1443    }
1444
1445    private class ImageBoxSelector extends CellImpl {
1446        boolean init = false;
1447        boolean active = true;
1448        SubsetRubberBandBox subsetBox;
1449        Set imageDomain;
1450        int domainLen_0;
1451        LineDrawing lastBox;
1452        ImageBoxSelector other;
1453        float maskVal;
1454        boolean earthCoordDomain = false;
1455        StatsTable myTable = null;
1456        int myTableIndex = 0;
1457
1458        ImageBoxSelector(SubsetRubberBandBox subsetBox, Set imageDomain, DisplayMaster master, Color color, float maskVal, StatsTable mst)
1459            throws VisADException, RemoteException {
1460            super();
1461            this.myTable = mst;
1462            myTableIndex = 0;
1463            if (color == Color.magenta) myTableIndex = 1;
1464            if (color == Color.green) myTableIndex = 2;
1465            if (color == Color.blue) myTableIndex = 3;
1466            this.subsetBox = subsetBox;
1467            this.imageDomain = imageDomain;
1468            int[] lens = ((Gridded2DSet)imageDomain).getLengths();
1469            this.maskVal = maskVal;
1470            domainLen_0 = lens[0];
1471            lastBox = new LineDrawing("last_box");
1472            lastBox.setColor(color);
1473            master.addDisplayable(lastBox);
1474            subsetBox.addAction(this);
1475            master.addDisplayable(subsetBox);
1476            RealTupleType rtt = ((SetType)imageDomain.getType()).getDomain();
1477            if (rtt.equals(RealTupleType.SpatialEarth2DTuple) ||
1478                rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1479                earthCoordDomain = true;
1480            }
1481        }
1482
1483        @Override public void doAction()
1484            throws VisADException, RemoteException
1485        {
1486            if (!init) {
1487                init = true;
1488                return;
1489            }
1490
1491            if (!active) {
1492                return;
1493            }
1494
1495            Gridded2DSet set = subsetBox.getBounds();
1496            float[][] corners = set.getSamples(false);
1497            float[][] coords = corners;
1498            if (corners == null) return;
1499
1500            if ((imageDomain instanceof Linear2DSet) || !earthCoordDomain) {
1501                coords = ((Gridded2DSet)imageDomain).valueToGrid(corners);
1502            }
1503
1504            float[] coords_0 = coords[0];
1505            float[] coords_1 = coords[1];
1506
1507            int low_0 = Math.round(Math.min(coords_0[0], coords_0[1]));
1508            int low_1 = Math.round(Math.min(coords_1[0], coords_1[1]));
1509            int hi_0  = Math.round(Math.max(coords_0[0], coords_0[1]));
1510            int hi_1  = Math.round(Math.max(coords_1[0], coords_1[1]));
1511
1512            int len_0 = (hi_0 - low_0) + 1;
1513            int len_1 = (hi_1 - low_1) + 1;
1514            int len = len_0*len_1;
1515
1516            float[][] markScatter = new float[3][len];
1517            int[] selected = new int[len];
1518
1519            for (int j=0; j<len_1; j++) {
1520                for (int i=0; i<len_0; i++) {
1521                    int idx = (j+low_1)*domainLen_0 + (i+low_0);
1522                    int k = j*len_0 + i;
1523                    markScatter[0][k] = scatterFieldRange[0][idx];
1524                    markScatter[1][k] = scatterFieldRange[1][idx];
1525                    markScatter[2][k] = maskVal;
1526                    selected[k] = idx;
1527                }
1528            }
1529
1530            int last_len = 0;
1531            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1532            float[][] tmp = new float[3][lastMark[0].length];
1533            for (int k=0; k<lastMark[0].length; k++) {
1534                if (lastMark[2][k] != maskVal) {
1535                    tmp[0][last_len] = lastMark[0][k];
1536                    tmp[1][last_len] = lastMark[1][k];
1537                    tmp[2][last_len] = lastMark[2][k];
1538                    last_len++;
1539                }
1540            }
1541
1542            float[][] newMarkScatter = new float[3][len+last_len];
1543            System.arraycopy(tmp[0], 0, newMarkScatter[0], 0, last_len);
1544            System.arraycopy(tmp[1], 0, newMarkScatter[1], 0, last_len);
1545            System.arraycopy(tmp[2], 0, newMarkScatter[2], 0, last_len);
1546            System.arraycopy(markScatter[0], 0, newMarkScatter[0], last_len, len);
1547            System.arraycopy(markScatter[1], 0, newMarkScatter[1], last_len, len);
1548            System.arraycopy(markScatter[2], 0, newMarkScatter[2], last_len, len);
1549
1550            Integer1DSet dset = new Integer1DSet(len+last_len);
1551            FlatField scatterFieldMark = new FlatField(
1552                new FunctionType(RealType.Generic,
1553                    new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1554
1555            scatterFieldMark.setSamples(newMarkScatter, false);
1556            scatterMarkDsp.setData(scatterFieldMark);
1557
1558            if (myTable != null) {
1559                total_area = JPythonMethods.computeSum(Area_field, selected);
1560                myTable.setPoints(markScatter, len, myTableIndex, total_area);
1561            }
1562
1563            updateBox();
1564        }
1565
1566        public void setActive(boolean active) {
1567            this.active = active;
1568        }
1569
1570        public void setVisible(boolean visible) throws VisADException, RemoteException {
1571            subsetBox.setVisible(visible);
1572            if (visible) {
1573                lastBox.setVisible(visible);
1574            }
1575        }
1576
1577        public void reset() throws VisADException, RemoteException {
1578            Gridded2DSet set2D =
1579                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1580                    new float[][] {{0},{0}}, 1);
1581            lastBox.setVisible(false);
1582            lastBox.setData(set2D);
1583
1584            float[][] lastMark = ((FlatField)scatterMarkDsp.getData()).getFloats(false);
1585            float[][] tmp = new float[3][lastMark[0].length];
1586            int cnt = 0;
1587            for (int k=0; k<lastMark[0].length; k++) {
1588                if (lastMark[2][k] != maskVal) {
1589                    tmp[0][cnt] = lastMark[0][k];
1590                    tmp[1][cnt] = lastMark[1][k];
1591                    tmp[2][cnt] = lastMark[2][k];
1592                    cnt++;
1593                }
1594            }
1595
1596            FlatField scatterFieldMark;
1597            if (cnt == 2) {
1598                Integer1DSet dset = new Integer1DSet(2);
1599                scatterFieldMark = new FlatField(
1600                    new FunctionType(RealType.Generic,
1601                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1602                float[][] markScatter = new float[3][2];
1603                for (int k=0; k<2; k++) {
1604                    markScatter[0][k] = scatterFieldRange[0][k];
1605                    markScatter[1][k] = scatterFieldRange[1][k];
1606                    markScatter[2][k] = 0;
1607                }
1608                scatterFieldMark.setSamples(markScatter, false);
1609            }
1610            else {
1611                Integer1DSet dset = new Integer1DSet(cnt);
1612                scatterFieldMark = new FlatField(
1613                    new FunctionType(RealType.Generic,
1614                        new RealTupleType(RealType.XAxis, RealType.YAxis, RealType.getRealType("mask"))), dset);
1615                float[][] markScatter = new float[3][cnt];
1616                for (int k=0; k<cnt; k++) {
1617                    markScatter[0][k] = tmp[0][k];
1618                    markScatter[1][k] = tmp[1][k];
1619                    markScatter[2][k] = tmp[2][k];
1620                }
1621                scatterFieldMark.setSamples(markScatter, false);
1622            }
1623
1624            scatterMarkDsp.setData(scatterFieldMark);
1625        }
1626
1627        public void setOther(ImageBoxSelector other) {
1628            this.other = other;
1629        }
1630
1631        public void updateBox() throws VisADException, RemoteException {
1632            Gridded3DSet set3D = subsetBox.getLastBox();
1633            float[][] samples = set3D.getSamples(false);
1634            Gridded2DSet set2D =
1635                new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple,
1636                    new float[][] {samples[0], samples[1]}, samples[0].length);
1637            lastBox.setData(set2D);
1638            other.updateBox(set2D);
1639        }
1640
1641        public void updateBox(Gridded2DSet set2D) throws VisADException, RemoteException {
1642            lastBox.setData(set2D);
1643        }
1644
1645    }
1646
1647    private class ScatterBoxSelector extends CellImpl {
1648       boolean init = false;
1649       double[] x_coords = new double[2];
1650       double[] y_coords = new double[2];
1651       RubberBandBox rbb;
1652       LineDrawing selectBox;
1653       boolean active = true;
1654       float maskVal = 0;
1655
1656       ScatterBoxSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1657         selectBox = new LineDrawing("select");
1658         selectBox.setColor(color);
1659
1660         rbb = new RubberBandBox(RealType.XAxis, RealType.YAxis, 1);
1661         rbb.setColor(color);
1662         rbb.addAction(this);
1663
1664         master.addDisplayable(rbb);
1665         master.addDisplayable(selectBox);
1666         this.maskVal = maskVal;
1667       }
1668
1669
1670       @Override public void doAction() throws VisADException, RemoteException {
1671         if (!init) {
1672           init = true;
1673           return;
1674         }
1675
1676         if (!active) {
1677           return;
1678         }
1679
1680         Gridded2DSet set = rbb.getBounds();
1681         float[] low = set.getLow();
1682         float[] hi = set.getHi();
1683         x_coords[0] = low[0];
1684         x_coords[1] = hi[0];
1685         y_coords[0] = low[1];
1686         y_coords[1] = hi[1];
1687                                                                                                                                                  
1688         SampledSet[] sets = new SampledSet[4];
1689         sets[0] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], hi[0]}, {low[1], low[1]}}, 2);
1690         sets[1] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], hi[0]}, {low[1], hi[1]}}, 2);
1691         sets[2] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{hi[0], low[0]}, {hi[1], hi[1]}}, 2);
1692         sets[3] = new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{low[0], low[0]}, {hi[1], low[1]}}, 2);
1693         UnionSet uset = new UnionSet(sets);
1694         selectBox.setData(uset);
1695
1696         try {
1697           histoField.markMaskFieldByRange(x_coords, y_coords, maskVal);
1698         } catch (Exception e) {
1699           logger.error("Problem changing histogram", e);
1700         }
1701       }
1702
1703       public void setVisible(boolean visible) throws VisADException, RemoteException {
1704         rbb.setVisible(visible);
1705         if (visible) {
1706           selectBox.setVisible(visible);
1707         }
1708       }
1709
1710       public void setActive(boolean active) {
1711         this.active = active;
1712       }
1713
1714       public void reset() throws Exception {
1715         if (!active) return;
1716         selectBox.setVisible(false);
1717         selectBox.setData(new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {{0f, 0f}, {0f, 0f}}, 2));
1718         histoField.resetMaskField(maskVal);
1719       }
1720   }
1721
1722   private class ScatterCurveSelector extends CellImpl implements DisplayListener {
1723     CurveDrawer curveDraw;
1724     boolean init = false;
1725     UnionSet last_uSet = null;
1726     boolean active = true;
1727     float maskVal = 0;
1728     LineDrawing selectCurve;
1729
1730     ScatterCurveSelector(DisplayMaster master, Color color, float maskVal) throws VisADException, RemoteException {
1731       curveDraw = new CurveDrawer(RealType.XAxis, RealType.YAxis, 1);
1732       curveDraw.setColor(color);
1733       curveDraw.setLineWidth(2);
1734       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1735            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1736            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1737        }, 1) }));
1738
1739       selectCurve = new LineDrawing("select");
1740       selectCurve.setColor(color);
1741       selectCurve.setLineWidth(2);
1742       master.addDisplayable(curveDraw);
1743       master.addDisplayable(selectCurve);
1744       this.maskVal = maskVal;
1745
1746       curveDraw.addAction(this);
1747       master.addDisplayListener(this);
1748     }
1749
1750     @Override public void displayChanged(DisplayEvent de)
1751            throws VisADException, RemoteException {
1752       if ((de.getId() == DisplayEvent.MOUSE_RELEASED) && (active)) {
1753         UnionSet uSet = curveDraw.getCurves();
1754         if (uSet == last_uSet) return;
1755         SampledSet[] sets = uSet.getSets();
1756         int s_idx = sets.length-1;
1757         float[][] crv;
1758                                                                                                                                                  
1759         crv = sets[s_idx].getSamples();
1760         last_uSet = new UnionSet(new SampledSet[] {sets[s_idx]});
1761         curveDraw.setCurves(last_uSet);
1762         selectCurve.setData(last_uSet);
1763
1764         try {
1765           histoField.clearMaskField(maskVal);
1766           histoField.markMaskFieldByCurve(crv, maskVal);
1767         } catch (Exception e) {
1768           logger.error("Problem handling displayChange", e);
1769         }
1770       }
1771     }
1772
1773     @Override public  void doAction() throws VisADException, RemoteException {
1774       if (!init) {
1775         init = true;
1776         return;
1777       }
1778     }
1779
1780     public void setVisible(boolean visible) throws VisADException, RemoteException {
1781       curveDraw.setVisible(visible);
1782     }
1783
1784     public void setActive(boolean active) {
1785       this.active = active;
1786     }
1787
1788     public void reset() throws Exception {
1789       if (!active) return;
1790       curveDraw.setData(new UnionSet(new Gridded2DSet[]{
1791            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1792            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1793        }, 1) }));
1794       selectCurve.setData(new UnionSet(new Gridded2DSet[]{
1795            new Gridded2DSet(RealTupleType.SpatialCartesian2DTuple, new float[][] {
1796            { scatterFieldRange[0][0] }, { scatterFieldRange[1][0]}
1797        }, 1) }));
1798       histoField.resetMaskField(maskVal);
1799     }
1800   }
1801
1802    private class BoxCurveSwitch implements ActionListener {
1803
1804        public BoxCurveSwitch() {
1805        }
1806
1807        @Override public void actionPerformed(ActionEvent ae) {
1808            String cmd = ae.getActionCommand();
1809            try {
1810                if (cmd.equals("Box")) {
1811                    selectByCurve = false;
1812                } else if (cmd.equals("Curve")) {
1813                    selectByCurve = true;
1814                }
1815            } catch (Exception e) {
1816                logger.error("Problem switching curve type", e);
1817            }
1818        }
1819    }
1820
1821    public static float[] minmax(float[] values) {
1822        float min =  Float.MAX_VALUE;
1823        float max = -Float.MAX_VALUE;
1824        for (int k = 0; k < values.length; k++) {
1825            float val = values[k];
1826            if ((val == val) && (val < Float.POSITIVE_INFINITY) && (val > Float.NEGATIVE_INFINITY)) {
1827                if (val < min) min = val;
1828                if (val > max) max = val;
1829            }
1830        }
1831        return new float[] {min, max};
1832    }
1833
1834    public boolean getIsLatLon(FlatField field) throws VisADException, RemoteException {
1835        boolean isLL = false;
1836        FunctionType fnc_type = (FunctionType) field.getType();
1837        RealTupleType rtt = fnc_type.getDomain();
1838        if (rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1839            isLL = true;
1840        } else if (!rtt.equals(RealTupleType.SpatialEarth2DTuple)) {
1841            rtt = fnc_type.getDomain().getCoordinateSystem().getReference();
1842            if ( rtt.equals(RealTupleType.LatitudeLongitudeTuple)) {
1843                isLL = true;
1844            }
1845        }
1846        return isLL;
1847    }
1848}