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