001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2017
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
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.HashMap;
038import java.util.Hashtable;
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.DirectDataChoice;
054import ucar.unidata.data.grid.GridUtil;
055import ucar.unidata.idv.DisplayConventions;
056import ucar.unidata.util.ColorTable;
057import ucar.unidata.util.Range;
058import ucar.unidata.view.geoloc.MapProjectionDisplay;
059import ucar.unidata.view.geoloc.MapProjectionDisplayJ3D;
060import ucar.visad.display.DisplayMaster;
061import ucar.visad.display.MapLines;
062
063import visad.BaseColorControl;
064import visad.CellImpl;
065import visad.FlatField;
066import visad.FunctionType;
067import visad.Gridded2DSet;
068import visad.RealType;
069import visad.SampledSet;
070import visad.ScalarMap;
071import visad.VisADException;
072import visad.data.mcidas.BaseMapAdapter;
073import visad.georef.MapProjection;
074
075import edu.wisc.ssec.mcidasv.control.LambertAEA;
076import edu.wisc.ssec.mcidasv.control.RGBCompositeControl;
077import edu.wisc.ssec.mcidasv.data.hydra.HydraContext;
078import edu.wisc.ssec.mcidasv.data.hydra.HydraRGBDisplayable;
079import edu.wisc.ssec.mcidasv.data.hydra.MultiDimensionSubset;
080import edu.wisc.ssec.mcidasv.data.hydra.MultiSpectralData;
081import edu.wisc.ssec.mcidasv.data.hydra.SubsetRubberBandBox;
082
083public class PreviewSelection extends DataSelectionComponent {
084        
085        private static final Logger logger = LoggerFactory.getLogger(PreviewSelection.class);
086        
087      DataChoice dataChoice;
088      FlatField image;
089      boolean isLL;
090      boolean formulaActive = false;
091      MapProjection sampleProjection;
092
093      double[] x_coords = new double[2];
094      double[] y_coords = new double[2];
095      boolean hasSubset = false;
096      boolean selectionOutOfBounds = false;
097      MapProjectionDisplayJ3D mapProjDsp;
098      DisplayMaster dspMaster;
099
100      DataSourceImpl dataSource;
101
102      DataCategory dataCategory;
103      private SubsetRubberBandBox rbb = null;
104
105      static SampledSet lines_outlsupu = null;
106      static SampledSet lines_outlsupw = null;
107      static SampledSet lines_outlhpol = null;
108                                    
109      HydraContext hydraContext = null;
110
111      public PreviewSelection() {
112        super("Region");
113      }
114
115      public PreviewSelection(final DataChoice dataChoice, FlatField image,
116             MapProjection sample) throws VisADException, RemoteException {
117        this(dataChoice, image, sample, null, null);
118      }
119
120      public PreviewSelection(final DataChoice dataChoice, FlatField image,
121             MapProjection sample, Range displayRange, byte[][] colorTable) throws VisADException, RemoteException {
122        super("Region");
123
124        this.dataChoice = dataChoice;
125        this.dataCategory = (DataCategory) dataChoice.getCategories().get(0);
126        this.dataSource = (DataSourceImpl) ((DirectDataChoice)dataChoice).getDataSource();
127        this.image = image;
128        this.sampleProjection = sample;
129        sample = getDataProjection();
130        
131        // TJJ Jul 2014
132        // by sharing a property via the active View Manager, we can tell if 
133        // this preview is part of an in-progress VIIRS Formula. If so, it 
134        // appears we need to use a shared HydraContext so our geographic
135        // coverage subset applies across channels.  The flag is set in
136        // the originating Control init, and reset after the Displayable
137        // was successfully returned.
138        
139        Hashtable ht = dataSource.getIdv().getViewManager().getProperties();
140        if (ht.containsKey(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG)) {
141                formulaActive = (boolean) ht.get(RGBCompositeControl.FORMULA_IN_PROGRESS_FLAG);
142        }
143
144        DisplayConventions dspConv = dataSource.getDataContext().getIdv().getDisplayConventions();
145
146        if (this.sampleProjection == null) {
147            this.sampleProjection = sample;
148        }
149
150        isLL = sampleProjection.isLatLonOrder();
151
152        mapProjDsp = new MapProjectionDisplayJ3D(MapProjectionDisplay.MODE_2Din3D);
153        mapProjDsp.enableRubberBanding(false);
154        dspMaster = mapProjDsp;
155        mapProjDsp.setMapProjection(sampleProjection);
156        RealType imageRangeType = 
157          (((FunctionType)image.getType()).getFlatRange().getRealComponents())[0];
158        HydraRGBDisplayable imageDsp = new HydraRGBDisplayable("image", imageRangeType, null, true, null);
159        imageDsp.setData(image);
160
161        dspMaster.addDisplayable(imageDsp);
162
163        MapLines mapLines  = new MapLines("maplines");
164        URL      mapSource =
165        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPU");
166        try {
167            if (lines_outlsupu == null) {
168              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
169              lines_outlsupu = (SampledSet) mapAdapter.getData();
170            }
171            mapLines.setMapLines(lines_outlsupu);
172            mapLines.setColor(java.awt.Color.cyan);
173            mapProjDsp.addDisplayable(mapLines);
174        } catch (Exception excp) {
175            logger.error("Can't open map file " + mapSource);
176            excp.printStackTrace();
177        }
178
179        mapLines  = new MapLines("maplines");
180        mapSource =
181        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLSUPW");
182        try {
183            if (lines_outlsupw == null) {
184              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
185              lines_outlsupw = (SampledSet) mapAdapter.getData();
186            }
187            mapLines.setMapLines(lines_outlsupw);
188            mapLines.setColor(java.awt.Color.cyan);
189            mapProjDsp.addDisplayable(mapLines);
190        } catch (Exception excp) {
191                logger.error("Can't open map file " + mapSource);
192            excp.printStackTrace();
193        }
194
195        mapLines  = new MapLines("maplines");
196        mapSource =
197        mapProjDsp.getClass().getResource("/auxdata/maps/OUTLHPOL");
198        try {
199            if (lines_outlhpol == null) {
200              BaseMapAdapter mapAdapter = new BaseMapAdapter(mapSource);
201              lines_outlhpol = (SampledSet) mapAdapter.getData();
202            }
203            mapLines.setMapLines(lines_outlhpol);
204            mapLines.setColor(java.awt.Color.cyan);
205            mapProjDsp.addDisplayable(mapLines);
206        } catch (Exception excp) {
207            logger.error("Can't open map file " + mapSource);
208            excp.printStackTrace();
209        }
210
211
212
213        Hashtable table = dataChoice.getProperties();
214        Enumeration keys = table.keys();
215        while (keys.hasMoreElements()) {
216           Object key = keys.nextElement();
217           if (key instanceof MultiDimensionSubset) {
218             hasSubset = true;
219             MultiDimensionSubset select = (MultiDimensionSubset) table.get(key);
220
221             if (formulaActive) {
222                 hydraContext = HydraContext.getHydraContext();
223             } else {
224                 hydraContext = HydraContext.getHydraContext(dataSource, dataCategory);
225             }
226             if (hydraContext.getMultiDimensionSubset() == null) {
227                hydraContext.setMultiDimensionSubset(select);
228             }
229           }
230        }
231
232        rbb = new SubsetRubberBandBox(isLL, image, ((MapProjectionDisplay)mapProjDsp).getDisplayCoordinateSystem(), 1);
233        rbb.setColor(Color.green);
234        rbb.addAction(new CellImpl() {
235          boolean init = false;
236          
237          public void doAction()
238             throws VisADException, RemoteException
239           {
240                  
241             if (!init) {
242               init = true;
243               return;
244             }
245             Gridded2DSet set = rbb.getBounds();
246
247             float[] low = set.getLow();
248             float[] hi = set.getHi();
249
250             // TJJ Apr 2014 
251             // The fact that we can even get here with invalid bounding boxes  
252             // (edges == Infinity) is another problem that should be investigated.
253             // For now we should at least let the user know they selected off 
254             // the valid data bounds
255             
256             if ((low[0] == Float.NEGATIVE_INFINITY) || (low[0] == Float.POSITIVE_INFINITY)) 
257                 selectionOutOfBounds = true;
258             if ((hi[0] == Float.NEGATIVE_INFINITY) || (hi[0] == Float.POSITIVE_INFINITY)) 
259                 selectionOutOfBounds = true;
260             if ((low[1] == Float.NEGATIVE_INFINITY) || (low[1] == Float.POSITIVE_INFINITY)) 
261                 selectionOutOfBounds = true;
262             if ((hi[1] == Float.NEGATIVE_INFINITY) || (hi[1] == Float.POSITIVE_INFINITY)) 
263                 selectionOutOfBounds = true;
264             
265                  if (selectionOutOfBounds) {
266                        JOptionPane.showMessageDialog(null, 
267                                        "Data selection is not valid, please select within preview bounds", 
268                                        "Data Selection Error", JOptionPane.ERROR_MESSAGE);
269                        selectionOutOfBounds = false;
270                        return;
271                  }
272             
273             // TJJ Mar 2014
274             // The checks below are because the subset rubber-band box selector is
275             // able to select regions outside the data bounds, which causes 
276             // errors.  The simplest solution was to check the selection bounds
277             // and constrain them if they go outside data bounds.
278             
279             x_coords[0] = low[0];
280             if (x_coords[0] < 0)  {
281                 logger.debug("Constraining X lo bound: " + low[0] + " to: " + 0);
282                 x_coords[0] = 0;
283             }
284             x_coords[1] = hi[0];
285             int lineMax = rbb.getLineMax();
286             if (x_coords[1] > lineMax) {
287                 logger.debug("Constraining X hi bound: " + hi[0] + " to: " + lineMax);
288                 x_coords[1] = lineMax;
289             }
290
291             y_coords[0] = low[1];
292             if (y_coords[0] < 0) {
293                 logger.debug("Constraining Y lo bound: " + low[1] + " to: " + 0);
294                 y_coords[0] = 0;
295             }
296             y_coords[1] = hi[1];
297             int elemMax = rbb.getElemMax();
298             if (y_coords[1] > elemMax) {
299                 logger.debug("Constraining Y hi bound: " + hi[1] + " to: " + elemMax);
300                 y_coords[1] = elemMax;
301             }
302
303             if (hasSubset) {
304               MultiDimensionSubset select = hydraContext.getMultiDimensionSubset();
305                 Map<String, double[]> map = select.getSubset();
306
307               double[] coords0 = map.get("Track");
308               coords0[0] = y_coords[0];
309               coords0[1] = y_coords[1];
310               coords0[2] = 1;
311               double[] coords1 = map.get("XTrack");
312               coords1[0] = x_coords[0];
313               coords1[1] = x_coords[1];
314               coords1[2] = 1;
315               
316               hydraContext.setMultiDimensionSubset(new MultiDimensionSubset(map));
317             }
318           }
319        });
320        dspMaster.addDisplayable(rbb);
321
322        ScalarMap colorMap = imageDsp.getColorMap();
323        Range[] range = GridUtil.fieldMinMax(this.image);
324        Range imageRange = range[0];
325        double max;
326        double min;
327        double dMax = imageRange.getMax();
328        double dMin = imageRange.getMin();
329        String name = this.dataChoice.getName();
330
331        float[][] clrTbl = BaseColorControl.initTableGreyWedge(new float[4][256], true);
332
333        if (name.endsWith("BRIT")) {
334           dMin = imageRange.getMin();
335           min = dMax;
336           max = dMin;
337        } 
338        else if (imageRangeType.getName().contains("Reflectance")) {
339           min = dMax;
340           max = 0.0;
341        }
342        else if (imageRangeType.getName().equals("BrightnessTemp")) {
343           max = dMax*1.06;
344           min = dMax * 0.74;
345        }
346        else {
347           Range rng = dspConv.getParamRange(name, null);
348           max = dMax;
349           min = dMin;
350           ColorTable ct = dspConv.getParamColorTable(name);
351           clrTbl = ct.getTable();
352        }
353        colorMap.setRange(min, max);
354
355        /*-  must to draw first so colorMap has a Control */
356        dspMaster.draw();
357
358        BaseColorControl clrCntrl = (BaseColorControl) colorMap.getControl();
359        clrCntrl.setTable(clrTbl);
360      }
361
362       public MapProjection getDataProjection() {
363         MapProjection mp = null;
364
365         if (image == null) return mp;
366
367         Rectangle2D rect = MultiSpectralData.getLonLatBoundingBox(image);
368         try {
369           mp = new LambertAEA(rect);
370         } catch (Exception e) {
371             e.printStackTrace();
372         }
373         return mp;
374      }
375
376       public JComponent doMakeContents() {
377           JPanel panel = new JPanel(new BorderLayout());
378           panel.add(BorderLayout.CENTER, dspMaster.getDisplayComponent());
379           return panel;
380       }
381                                                                                                                                             
382      public void applyToDataSelection(DataSelection dataSelection) {
383          
384          if (hasSubset) {
385                  Hashtable table = dataChoice.getProperties();
386                  table.put(MultiDimensionSubset.key, hydraContext.getMultiDimensionSubset());
387
388                  table = dataSelection.getProperties();
389                  table.put(MultiDimensionSubset.key, hydraContext.getMultiDimensionSubset());
390
391                  dataChoice.setDataSelection(dataSelection);
392          }
393         
394      }
395
396      /**
397       * Enable or disable region subsetting
398       * 
399       * @param b true or false 
400       */
401      
402      public void enableSubsetting(boolean b) {
403          try {
404                  rbb.setVisible(b);
405          } catch (RemoteException | VisADException e) {
406                  e.printStackTrace();
407          }
408      }
409        
410  }