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 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 }