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;
030
031import java.awt.BorderLayout;
032import java.awt.Color;
033import java.awt.geom.Rectangle2D;
034import java.net.URL;
035import java.rmi.RemoteException;
036import java.util.Enumeration;
037import java.util.Hashtable;
038import java.util.List;
039import java.util.Map;
040
041import javax.swing.JComponent;
042import javax.swing.JOptionPane;
043import javax.swing.JPanel;
044
045import org.slf4j.Logger;
046import org.slf4j.LoggerFactory;
047
048import ucar.unidata.data.DataCategory;
049import ucar.unidata.data.DataChoice;
050import ucar.unidata.data.DataSelection;
051import ucar.unidata.data.DataSelectionComponent;
052import ucar.unidata.data.DataSourceImpl;
053import ucar.unidata.data.DerivedDataChoice;
054import ucar.unidata.data.DirectDataChoice;
055import ucar.unidata.data.grid.GridUtil;
056import ucar.unidata.idv.DisplayConventions;
057import ucar.unidata.util.ColorTable;
058import ucar.unidata.util.Range;
059import ucar.unidata.view.geoloc.MapProjectionDisplay;
060import ucar.unidata.view.geoloc.MapProjectionDisplayJ3D;
061import ucar.visad.display.DisplayMaster;
062import ucar.visad.display.MapLines;
063
064import visad.BaseColorControl;
065import visad.CellImpl;
066import visad.FlatField;
067import visad.FunctionType;
068import visad.Gridded2DSet;
069import visad.RealType;
070import visad.SampledSet;
071import visad.ScalarMap;
072import visad.VisADException;
073import visad.data.mcidas.BaseMapAdapter;
074import visad.georef.MapProjection;
075
076import edu.wisc.ssec.mcidasv.control.LambertAEA;
077import edu.wisc.ssec.mcidasv.control.RGBCompositeControl;
078import edu.wisc.ssec.mcidasv.data.hydra.HydraContext;
079import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
080import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
081import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
082import edu.wisc.ssec.mcidasv.data.hydra.SubsetRubberBandBox;
083
084public class PreviewSelection extends DataSelectionComponent {
085        
086        private static final Logger logger =
087        LoggerFactory.getLogger(PreviewSelection.class);
088        
089      DataChoice dataChoice;
090      FlatField image;
091      boolean isLL;
092      boolean formulaActive = false;
093      MapProjection sampleProjection;
094
095      double[] x_coords = new double[2];
096      double[] y_coords = new double[2];
097      boolean hasSubset = false;
098      boolean selectionOutOfBounds = false;
099      MapProjectionDisplayJ3D mapProjDsp;
100      DisplayMaster dspMaster;
101
102      DataSourceImpl dataSource;
103
104      DataCategory dataCategory;
105      private SubsetRubberBandBox rbb = null;
106
107      static SampledSet lines_outlsupu = null;
108      static SampledSet lines_outlsupw = null;
109      static SampledSet lines_outlhpol = null;
110                                    
111      HydraContext hydraContext = null;
112
113      public PreviewSelection() {
114        super("Region");
115      }
116
117      public PreviewSelection(final DataChoice dataChoice, FlatField image,
118             MapProjection sample) throws VisADException, RemoteException {
119        this(dataChoice, image, sample, null, null);
120      }
121
122      public PreviewSelection(final DataChoice dataChoice, FlatField image,
123             MapProjection sample, Range displayRange, byte[][] colorTable) throws VisADException, RemoteException {
124        super("Region");
125
126        this.dataChoice = dataChoice;
127        this.dataCategory = (DataCategory) dataChoice.getCategories().get(0);
128        if (dataChoice instanceof DerivedDataChoice) {
129            // Use the first in the list
130            List<DataChoice> children = ((DerivedDataChoice) dataChoice).getChoices();
131            DataChoice dc = children.get(0);
132            this.dataSource = (DataSourceImpl) ((DirectDataChoice) dc).getDataSource();
133        } else {
134           this.dataSource = (DataSourceImpl) ((DirectDataChoice) dataChoice).getDataSource();
135        }
136        this.image = image;
137        this.sampleProjection = sample;
138        sample = getDataProjection();
139        
140        // TJJ Jul 2014
141        // by sharing a property via the active View Manager, we can tell if 
142        // this preview is part of an in-progress VIIRS Formula. If so, it 
143        // appears we need to use a shared HydraContext so our geographic
144        // coverage subset applies across channels.  The flag is set in
145        // the originating Control init, and reset after the Displayable
146        // was successfully returned.
147        
148        Hashtable ht = dataSource.getIdv().getViewManager().getProperties();
149        if (ht.containsKey(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG)) {
150                formulaActive = (boolean) ht.get(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG);
151        }
152
153        DisplayConventions dspConv = dataSource.getDataContext().getIdv().getDisplayConventions();
154
155        if (this.sampleProjection == null) {
156            this.sampleProjection = sample;
157        }
158
159        isLL = sampleProjection.isLatLonOrder();
160
161        mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
162        mapProjDsp.enableRubberBanding(false);
163        dspMaster = mapProjDsp;
164        mapProjDsp.setMapProjection(sampleProjection);
165        RealType imageRangeType = 
166          (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
167        HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, true, null);
168        imageDsp.setData(image);
169
170        dspMaster.addDisplayable(imageDsp);
171
172        MapLines mapLines  = new MapLines("maplines");
173        URL      mapSource =
174        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
175        try {
176            if (lines_outlsupu == null) {
177              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
178              lines_outlsupu = (SampledSet) mapAdapter.getData();
179            }
180            mapLines.setMapLines(lines_outlsupu);
181            mapLines.setColor(java.awt.Color.cyan);
182            mapProjDsp.addDisplayable(mapLines);
183        } catch (Exception excp) {
184            logger.error("Can't open map file " + mapSource, excp);
185        }
186
187        mapLines  = new MapLines("maplines");
188        mapSource =
189        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
190        try {
191            if (lines_outlsupw == null) {
192              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
193              lines_outlsupw = (SampledSet) mapAdapter.getData();
194            }
195            mapLines.setMapLines(lines_outlsupw);
196            mapLines.setColor(java.awt.Color.cyan);
197            mapProjDsp.addDisplayable(mapLines);
198        } catch (Exception excp) {
199                logger.error("Can't open map file " + mapSource, excp);
200        }
201
202        mapLines  = new MapLines("maplines");
203        mapSource =
204        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
205        try {
206            if (lines_outlhpol == null) {
207              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
208              lines_outlhpol = (SampledSet) mapAdapter.getData();
209            }
210            mapLines.setMapLines(lines_outlhpol);
211            mapLines.setColor(java.awt.Color.cyan);
212            mapProjDsp.addDisplayable(mapLines);
213        } catch (Exception excp) {
214            logger.error("Can't open map file " + mapSource, excp);
215        }
216
217
218
219        Hashtable table = dataChoice.getProperties();
220        Enumeration keys = table.keys();
221        while (keys.hasMoreElements()) {
222           Object key = keys.nextElement();
223           if (key instanceof MultiDimensionSubset) {
224             hasSubset = true;
225             MultiDimensionSubset select = (MultiDimensionSubset) table.get(key);
226
227             if (formulaActive) {
228                 hydraContext = HydraContext.getHydraContext();
229             } else {
230                 hydraContext = HydraContext.getHydraContext(dataSource, dataCategory);
231             }
232             if (hydraContext.getMultiDimensionSubset() == null) {
233                hydraContext.setMultiDimensionSubset(select);
234             }
235           }
236        }
237
238        rbb = new SubsetRubberBandBox(isLL, image, ((MapProjectionDisplay)mapProjDsp).getDisplayCoordinateSystem(), 1);
239        rbb.setColor(Color.green);
240        rbb.addAction(new CellImpl() {
241          boolean init = false;
242          
243          public void doAction()
244             throws VisADException, RemoteException
245           {
246
247             // TJJ Jun 2020 - feels like this should fix Inq #1446
248             // set bounds to preview max on init is what I'm trying to do
249             if (! init) {
250
251                 init = true;
252
253                 // Skip subset adjustment for CrIS since we don't support rubber-band box subsetting
254                 // for that sensor (not a big drawback given the low spatial resolution).
255                 String dataChoiceName = dataChoice.getName().toUpperCase();
256                 if (dataChoiceName.startsWith("CRIS")) return;
257
258                 x_coords[0] = 0;
259                 x_coords[1] = rbb.getLineMax();
260                 y_coords[0] = 0;
261                 y_coords[1] = rbb.getElemMax();
262
263                 if (hasSubset) {
264                     MultiDimensionSubset select = hydraContext.getMultiDimensionSubset();
265                     Map<String, double[]> map = select.getSubset();
266
267                     double[] coords0 = map.get("Track");
268                     coords0[0] = y_coords[0];
269                     coords0[1] = y_coords[1];
270                     coords0[2] = 1;
271                     double[] coords1 = map.get("XTrack");
272                     coords1[0] = x_coords[0];
273                     coords1[1] = x_coords[1];
274                     coords1[2] = 1;
275
276                     hydraContext.setMultiDimensionSubset(new MultiDimensionSubset(map));
277                 }
278                 return;
279             }
280
281             Gridded2DSet set = rbb.getBounds();
282
283             float[] low = set.getLow();
284             float[] hi = set.getHi();
285
286             // TJJ Apr 2014 
287             // The fact that we can even get here with invalid bounding boxes  
288             // (edges == Infinity) is another problem that should be investigated.
289             // For now we should at least let the user know they selected off 
290             // the valid data bounds
291             
292             if ((low[0] == Float.NEGATIVE_INFINITY) || (low[0] == Float.POSITIVE_INFINITY)) 
293                 selectionOutOfBounds = true;
294             if ((hi[0] == Float.NEGATIVE_INFINITY) || (hi[0] == Float.POSITIVE_INFINITY)) 
295                 selectionOutOfBounds = true;
296             if ((low[1] == Float.NEGATIVE_INFINITY) || (low[1] == Float.POSITIVE_INFINITY)) 
297                 selectionOutOfBounds = true;
298             if ((hi[1] == Float.NEGATIVE_INFINITY) || (hi[1] == Float.POSITIVE_INFINITY)) 
299                 selectionOutOfBounds = true;
300             
301                  if (selectionOutOfBounds) {
302                        JOptionPane.showMessageDialog(null, 
303                                        "Data selection is not valid, please select within preview bounds", 
304                                        "Data Selection Error", JOptionPane.ERROR_MESSAGE);
305                        selectionOutOfBounds = false;
306                        return;
307                  }
308             
309             // TJJ Mar 2014
310             // The checks below are because the subset rubber-band box selector is
311             // able to select regions outside the data bounds, which causes 
312             // errors.  The simplest solution was to check the selection bounds
313             // and constrain them if they go outside data bounds.
314             
315             x_coords[0] = low[0];
316             if (x_coords[0] < 0)  {
317                 logger.debug("Constraining X lo bound: " + low[0] + " to: " + 0);
318                 x_coords[0] = 0;
319             }
320             x_coords[1] = hi[0];
321             int lineMax = rbb.getLineMax();
322             if (x_coords[1] > lineMax) {
323                 logger.debug("Constraining X hi bound: " + hi[0] + " to: " + lineMax);
324                 x_coords[1] = lineMax;
325             }
326
327             y_coords[0] = low[1];
328             if (y_coords[0] < 0) {
329                 logger.debug("Constraining Y lo bound: " + low[1] + " to: " + 0);
330                 y_coords[0] = 0;
331             }
332             y_coords[1] = hi[1];
333             int elemMax = rbb.getElemMax();
334             if (y_coords[1] > elemMax) {
335                 logger.debug("Constraining Y hi bound: " + hi[1] + " to: " + elemMax);
336                 y_coords[1] = elemMax;
337             }
338
339             if (hasSubset) {
340               MultiDimensionSubset select = hydraContext.getMultiDimensionSubset();
341                 Map<String, double[]> map = select.getSubset();
342
343               double[] coords0 = map.get("Track");
344               coords0[0] = y_coords[0];
345               coords0[1] = y_coords[1];
346               coords0[2] = 1;
347               double[] coords1 = map.get("XTrack");
348               coords1[0] = x_coords[0];
349               coords1[1] = x_coords[1];
350               coords1[2] = 1;
351               
352               hydraContext.setMultiDimensionSubset(new MultiDimensionSubset(map));
353             }
354           }
355        });
356        dspMaster.addDisplayable(rbb);
357
358        ScalarMap colorMap = imageDsp.getColorMap();
359        Range[] range = GridUtil.fieldMinMax(this.image);
360        Range imageRange = range[0];
361        double max;
362        double min;
363        double dMax = imageRange.getMax();
364        double dMin = imageRange.getMin();
365        String name = this.dataChoice.getName();
366
367        float[][] clrTbl = BaseColorControl.initTableGreyWedge(new float[4][256], true);
368
369        if (name.endsWith("BRIT")) {
370           dMin = imageRange.getMin();
371           min = dMax;
372           max = dMin;
373        } 
374        else if (imageRangeType.getName().contains("Reflectance")) {
375           min = dMax;
376           max = 0.0;
377        }
378        else if (imageRangeType.getName().equals("BrightnessTemp")) {
379           max = dMax*1.06;
380           min = dMax * 0.74;
381        }
382        else {
383           Range rng = dspConv.getParamRange(name, null);
384           max = dMax;
385           min = dMin;
386           ColorTable ct = dspConv.getParamColorTable(name);
387           clrTbl = ct.getTable();
388        }
389        colorMap.setRange(min, max);
390
391        /*-  must to draw first so colorMap has a Control */
392        dspMaster.draw();
393
394        BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
395        clrCntrl.setTable(clrTbl);
396      }
397
398       public MapProjection getDataProjection() {
399         MapProjection mp = null;
400
401         if (image == null) return mp;
402
403         Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
404         try {
405           mp = new LambertAEA(rect);
406         } catch (Exception e) {
407             logger.error("Problem creating LambertAEA", e);
408         }
409         return mp;
410      }
411
412       public JComponent doMakeContents() {
413           JPanel panel = new JPanel(new BorderLayout());
414           panel.add(BorderLayout.CENTER, dspMaster.getDisplayComponent());
415           return panel;
416       }
417                                                                                                                                             
418      public void applyToDataSelection(DataSelection dataSelection) {
419          
420          if (hasSubset) {
421                  Hashtable table = dataChoice.getProperties();
422                  table.put(MultiDimensionSubset.key, hydraContext.getMultiDimensionSubset());
423
424                  table = dataSelection.getProperties();
425                  table.put(MultiDimensionSubset.key, hydraContext.getMultiDimensionSubset());
426
427                  dataChoice.setDataSelection(dataSelection);
428          }
429         
430      }
431
432      /**
433       * Enable or disable region subsetting
434       * 
435       * @param b true or false 
436       */
437      public void enableSubsetting(boolean b) {
438          try {
439                  rbb.setVisible(b);
440          } catch (RemoteException | VisADException e) {
441              logger.trace("Problem changing RBB visibility", e);
442          }
443      }
444        
445  }