001/* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2016 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.hydra; 030 031import edu.wisc.ssec.mcidasv.Constants; 032import edu.wisc.ssec.mcidasv.McIDASV; 033import edu.wisc.ssec.mcidasv.PersistenceManager; 034import edu.wisc.ssec.mcidasv.data.HydraDataSource; 035import edu.wisc.ssec.mcidasv.data.PreviewSelection; 036import edu.wisc.ssec.mcidasv.data.QualityFlag; 037 038import java.io.ByteArrayInputStream; 039import java.io.File; 040import java.io.FilenameFilter; 041import java.rmi.RemoteException; 042import java.text.SimpleDateFormat; 043import java.util.ArrayList; 044import java.util.Date; 045import java.util.Enumeration; 046import java.util.HashMap; 047import java.util.Hashtable; 048import java.util.Iterator; 049import java.util.LinkedHashMap; 050import java.util.LinkedHashSet; 051import java.util.List; 052import java.util.Map; 053import java.util.Set; 054import java.util.SimpleTimeZone; 055import java.util.StringTokenizer; 056 057import javax.swing.JCheckBox; 058import javax.swing.JOptionPane; 059 060import org.jdom2.Document; 061import org.jdom2.Element; 062import org.jdom2.Namespace; 063import org.jdom2.output.XMLOutputter; 064import org.slf4j.Logger; 065import org.slf4j.LoggerFactory; 066 067import ucar.ma2.ArrayFloat; 068import ucar.ma2.DataType; 069import ucar.nc2.Attribute; 070import ucar.nc2.Dimension; 071import ucar.nc2.Group; 072import ucar.nc2.NetcdfFile; 073import ucar.nc2.Variable; 074import ucar.nc2.dataset.VariableDS; 075import ucar.unidata.data.DataCategory; 076import ucar.unidata.data.DataChoice; 077import ucar.unidata.data.DataSelection; 078import ucar.unidata.data.DataSelectionComponent; 079import ucar.unidata.data.DataSourceDescriptor; 080import ucar.unidata.data.DirectDataChoice; 081import ucar.unidata.data.GeoLocationInfo; 082import ucar.unidata.data.GeoSelection; 083import ucar.unidata.data.grid.GridUtil; 084import ucar.unidata.idv.IdvPersistenceManager; 085import ucar.unidata.util.Misc; 086import visad.Data; 087import visad.DateTime; 088import visad.DerivedUnit; 089import visad.FieldImpl; 090import visad.FlatField; 091import visad.FunctionType; 092import visad.RealType; 093import visad.SampledSet; 094import visad.Unit; 095import visad.VisADException; 096import visad.data.units.NoSuchUnitException; 097import visad.data.units.ParseException; 098import visad.data.units.Parser; 099import visad.util.Util; 100 101/** 102 * A data source for NPOESS Preparatory Project (Suomi NPP) data 103 * This will probably move, but we are placing it here for now 104 * since we are leveraging some existing code used for HYDRA. 105 */ 106 107public class SuomiNPPDataSource extends HydraDataSource { 108 109 private static final Logger logger = LoggerFactory.getLogger(SuomiNPPDataSource.class); 110 111 /** Sources file */ 112 protected String filename; 113 114 // for loading bundles, store granule lists and geo lists here 115 protected List<String> oldSources = new ArrayList<>(); 116 protected List<String> geoSources = new ArrayList<>(); 117 118 // integrity map for grouping sets/aggregations of selected products 119 Map<String, List<String>> filenameMap = null; 120 121 protected MultiDimensionReader nppAggReader; 122 123 protected MultiDimensionAdapter[] adapters = null; 124 125 private List<MultiSpectralData> msd_CrIS = new ArrayList<>(); 126 private List<MultiSpectralData> multiSpectralData = new ArrayList<>(); 127 private Map<String, MultiSpectralData> msdMap = new HashMap<>(); 128 private Map<String, QualityFlag> qfMap = new HashMap<>(); 129 private Map<String, float[]> lutMap = new HashMap<>(); 130 131 private static final String DATA_DESCRIPTION = "Suomi NPP Data"; 132 133 // instrument related variables and flags 134 Attribute instrumentName = null; 135 private String productName = null; 136 137 // product related variables and flags 138 String whichEDR = ""; 139 140 // for now, we are only handling CrIS variables that match this filter and SCAN dimensions 141 private String crisFilter = "ES_Real"; 142 143 // for now, we are only handling OMPS variables that match this filter and SCAN dimensions 144 private String ompsFilter = "Radiance"; 145 146 private Map<String, double[]> defaultSubset; 147 public TrackAdapter track_adapter; 148 149 private List categories; 150 private boolean isCombinedProduct = false; 151 private boolean nameHasBeenSet = false; 152 153 // need our own separator char since it's always Unix-style in the Suomi NPP files 154 private static final String SEPARATOR_CHAR = "/"; 155 156 // date formatter for NASA L1B data, ex 2016-02-07T00:06:00.000Z 157 SimpleDateFormat sdfNASA = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"); 158 159 // LUTs for NASA L1B data 160 float[] m12LUT = null; 161 float[] m13LUT = null; 162 float[] m14LUT = null; 163 float[] m15LUT = null; 164 float[] m16LUT = null; 165 float[] i04LUT = null; 166 float[] i05LUT = null; 167 168 // Map to match NASA variables to units (XML Product Profiles used for NOAA) 169 Map<String, String> unitsNASA = new HashMap<String, String>(); 170 171 // date formatter for converting Suomi NPP day/time to something we can use 172 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS"); 173 174 // date formatter for how we want to show granule day/time on display 175 SimpleDateFormat sdfOut = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); 176 177 // MJH keep track of date to add time dim to FieldImpl 178 Date theDate; 179 180 /** 181 * Zero-argument constructor for construction via unpersistence. 182 */ 183 184 public SuomiNPPDataSource() { 185 } 186 187 public SuomiNPPDataSource(String fileName) throws VisADException { 188 this(null, Misc.newList(fileName), null); 189 logger.debug("filename only constructor call.."); 190 } 191 192 /** 193 * Construct a new Suomi NPP HDF5 data source. 194 * @param descriptor descriptor for this {@code DataSource} 195 * @param fileName name of the hdf file to read 196 * @param properties hashtable of properties 197 * 198 * @throws VisADException problem creating data 199 */ 200 201 public SuomiNPPDataSource(DataSourceDescriptor descriptor, 202 String fileName, Hashtable properties) 203 throws VisADException { 204 this(descriptor, Misc.newList(fileName), properties); 205 logger.debug("SuomiNPPDataSource called, single file selected: " + fileName); 206 } 207 208 /** 209 * Construct a new Suomi NPP HDF5 data source. 210 * 211 * @param descriptor Descriptor for this {@code DataSource}. 212 * @param newSources List of filenames. 213 * @param properties Hashtable of properties. 214 * 215 * @throws VisADException problem creating data 216 */ 217 218 public SuomiNPPDataSource(DataSourceDescriptor descriptor, 219 List<String> newSources, Hashtable properties) 220 throws VisADException { 221 super(descriptor, newSources, DATA_DESCRIPTION, properties); 222 logger.debug("SuomiNPPDataSource constructor called, file count: " + sources.size()); 223 224 filename = (String) sources.get(0); 225 setDescription("Suomi NPP"); 226 227 // NASA data is UTC, pre-set time zone 228 SimpleTimeZone stz = new SimpleTimeZone(0, "UTC"); 229 sdfNASA.setTimeZone(stz);; 230 231 // build the filename map - matches each product to set of files for that product 232 filenameMap = new HashMap<>(); 233 234 // Pass 1, populate the list of products selected 235 for (Object o : sources) { 236 String filename = (String) o; 237 // first five characters of any product go together 238 int lastSeparator = filename.lastIndexOf(File.separatorChar); 239 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 240 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 241 if (! filenameMap.containsKey(prodStr)) { 242 List<String> l = new ArrayList<String>(); 243 filenameMap.put(prodStr, l); 244 } 245 } 246 247 // pass 2, create a list of files for each product in this data source 248 for (Object o : sources) { 249 String filename = (String) o; 250 // first five characters of any product go together 251 int lastSeparator = filename.lastIndexOf(File.separatorChar); 252 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 253 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 254 List l = (List) filenameMap.get(prodStr); 255 l.add(filename); 256 filenameMap.put(prodStr, l); 257 } 258 259 versionCheck(); 260 setup(); 261 initQfTranslations(); 262 } 263 264 // alert user about possible VIIRS plugin compatibility issues 265 private void versionCheck() { 266 boolean pluginDialog = getIdv().getStore().get(Constants.PREF_VIIRS_PLUGIN, false); 267 // don't create a dialog though if we are running in background/offscreen mode 268 boolean offScreen = getIdv().getArgsManager().getIsOffScreen(); 269 if (! offScreen) { 270 if (! pluginDialog) { 271 String msg = "There has been an update to the VIIRS Formulas plugin.\n" + 272 "If you use the plugin, you will need to uninstall the currently installed\n" + 273 "version of the plugin, and install the plugin called \"VIIRS Formulas\"."; 274 JCheckBox jcbPlugin = new JCheckBox("Do not show this message again"); 275 Object[] params = { msg, jcbPlugin }; 276 JOptionPane.showMessageDialog(null, params, "Plugin Compatibility Notice", JOptionPane.OK_OPTION); 277 boolean dontShow = jcbPlugin.isSelected(); 278 getIdv().getStore().put(Constants.PREF_VIIRS_PLUGIN, dontShow); 279 } 280 } else { 281 logger.warn("Make sure your VIIRS plugin is current, there was an update with McV 1.5"); 282 } 283 } 284 285 public void setup() throws VisADException { 286 287 // which format, NASA or NOAA? 288 boolean isNOAA = false; 289 290 // store filenames for possible bundle unpersistence 291 for (Object o : sources) { 292 oldSources.add((String) o); 293 } 294 295 // time zone for product labels 296 SimpleTimeZone stz = new SimpleTimeZone(0, "GMT"); 297 sdf.setTimeZone(stz); 298 sdfOut.setTimeZone(stz); 299 300 // looking to populate 3 things - path to lat, path to lon, path to relevant products 301 String pathToLat = null; 302 String pathToLon = null; 303 Set<String> pathToProducts = new LinkedHashSet<>(); 304 305 // flag to differentiate VIIRS from one of the other Suomi sensors 306 boolean isVIIRS = true; 307 308 // check source filenames to see if this is a combined product. everything 309 // from last file separator to first underscore should be product info 310 int lastSeparator = filename.lastIndexOf(File.separatorChar); 311 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 312 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 313 // only do this check for NOAA data 314 if (filename.endsWith(".h5")) { 315 isNOAA = true; 316 StringTokenizer st = new StringTokenizer(prodStr, "-"); 317 logger.debug("SNPPDS check for embedded GEO, tokenizing: " + prodStr); 318 while (st.hasMoreTokens()) { 319 String singleProd = st.nextToken(); 320 for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) { 321 if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) { 322 logger.debug("Setting isCombinedProduct true, Found embedded GEO: " + singleProd); 323 isCombinedProduct = true; 324 break; 325 } 326 } 327 } 328 } 329 330 // various metatdata we'll need to gather on a per-product basis 331 Map<String, String> unsignedFlags = new LinkedHashMap<>(); 332 Map<String, String> unpackFlags = new LinkedHashMap<>(); 333 334 // geo product IDs for each granule 335 Set<String> geoProductIDs = new LinkedHashSet<>(); 336 337 // aggregations will use sets of NetCDFFile readers 338 List<NetCDFFile> ncdfal = new ArrayList<>(); 339 340 // we should be able to find an XML Product Profile for each data/product type 341 SuomiNPPProductProfile nppPP = null; 342 // and also Profile metadata for geolocation variables 343 boolean haveGeoMetaData = false; 344 345 // number of source granules which make up the data source 346 int granuleCount = 1; 347 348 try { 349 350 nppPP = new SuomiNPPProductProfile(); 351 352 // for each source file provided, find the appropriate geolocation, 353 // get the nominal time and various other granule-level metadata 354 Iterator keyIterator = filenameMap.keySet().iterator(); 355 while (keyIterator.hasNext()) { 356 String keyStr = (String) keyIterator.next(); 357 List fileNames = (List) filenameMap.get(keyStr); 358 granuleCount = fileNames.size(); 359 setProperty(Constants.PROP_GRANULE_COUNT, granuleCount + " Granule"); 360 for (int fileCount = 0; fileCount < granuleCount; fileCount++) { 361 // need to open the main NetCDF file to determine the geolocation product 362 NetcdfFile ncfile = null; 363 String fileAbsPath = null; 364 try { 365 fileAbsPath = (String) fileNames.get(fileCount); 366 logger.debug("Trying to open file: " + fileAbsPath); 367 ncfile = NetcdfFile.open(fileAbsPath); 368 if (! isCombinedProduct) { 369 if (isNOAA) { 370 Attribute a = ncfile.findGlobalAttribute("N_GEO_Ref"); 371 logger.debug("Value of GEO global attribute: " + a.getStringValue()); 372 String tmpGeoProductID = a.getStringValue(); 373 geoProductIDs.add(tmpGeoProductID); 374 } else { 375 geoProductIDs.add(keyStr.replace("L1B", "GEO")); 376 } 377 } 378 Group rg = ncfile.getRootGroup(); 379 380 List<Group> gl = rg.getGroups(); 381 if (gl != null) { 382 for (Group g : gl) { 383 logger.trace("Group name: " + g.getFullName()); 384 if (isNOAA) { 385 // when we find the Data_Products group, go down another group level and pull out 386 // what we will use for nominal day and time (for now anyway). 387 // XXX TJJ fileCount check is so we don't count the GEO file in time array! 388 if (g.getFullName().contains("Data_Products") 389 && (fileCount != fileNames.size())) { 390 List<Group> dpg = g.getGroups(); 391 392 // cycle through once looking for XML Product Profiles 393 for (Group subG : dpg) { 394 395 String subName = subG.getFullName(); 396 // use actual product, not geolocation, to id XML Product Profile 397 if (!subName.contains("-GEO")) { 398 // determine the instrument name (VIIRS, ATMS, CrIS, OMPS) 399 instrumentName = subG.findAttribute("Instrument_Short_Name"); 400 401 // note any EDR products, will need to check for and remove 402 // fill scans later 403 Attribute adtt = subG.findAttribute("N_Dataset_Type_Tag"); 404 if (adtt != null) { 405 String baseName = adtt.getStringValue(); 406 if ((baseName != null) && (baseName.equals("EDR"))) { 407 // have to loop through sub groups variables to determine band 408 List<Variable> tmpVar = subG.getVariables(); 409 for (Variable v : tmpVar) { 410 // if Imagery EDR attribute for band is specified, save it 411 Attribute mBand = v.findAttribute("Band_ID"); 412 if (mBand != null) { 413 whichEDR = mBand.getStringValue(); 414 } 415 } 416 } 417 } 418 419 // This is also where we find the attribute which tells us which 420 // XML Product Profile to use! 421 Attribute axpp = subG.findAttribute("N_Collection_Short_Name"); 422 if (axpp != null) { 423 String baseName = axpp.getStringValue(); 424 productName = baseName; 425 String productProfileFileName = nppPP 426 .getProfileFileName(baseName); 427 logger.trace("Found profile: " + productProfileFileName); 428 if (productProfileFileName == null) { 429 throw new Exception( 430 "XML Product Profile not found in catalog"); 431 } 432 try { 433 nppPP.addMetaDataFromFile(productProfileFileName); 434 } catch (Exception nppppe) { 435 logger.error("Error parsing XML Product Profile: " 436 + productProfileFileName); 437 throw new Exception( 438 "XML Product Profile Error", 439 nppppe); 440 } 441 } 442 } 443 } 444 445 // 2nd pass through sub-group to extract date/time for aggregation 446 for (Group subG : dpg) { 447 List<Variable> vl = subG.getVariables(); 448 for (Variable v : vl) { 449 Attribute aDate = v.findAttribute("AggregateBeginningDate"); 450 Attribute aTime = v.findAttribute("AggregateBeginningTime"); 451 // did we find the attributes we are looking for? 452 if ((aDate != null) && (aTime != null)) { 453 // set time for display to day/time of 1st granule examined 454 if (! nameHasBeenSet) { 455 String sDate = aDate.getStringValue(); 456 String sTime = aTime.getStringValue(); 457 logger.debug("For day/time, using: " + sDate 458 + sTime.substring(0, sTime.indexOf('Z') - 3)); 459 Date d = sdf.parse(sDate 460 + sTime.substring(0, sTime.indexOf('Z') - 3)); 461 theDate = d; 462 setName(instrumentName.getStringValue() + " " 463 + sdfOut.format(d)); 464 nameHasBeenSet = true; 465 } 466 break; 467 } 468 } 469 } 470 if (! nameHasBeenSet) { 471 throw new VisADException( 472 "No date time found in Suomi NPP granule"); 473 } 474 } 475 } else { 476 // NASA data - date/time from global attribute 477 // set time for display to day/time of 1st granule examined 478 Attribute timeStartNASA = ncfile.findGlobalAttribute("time_coverage_start"); 479 Date d = sdfNASA.parse(timeStartNASA.getStringValue()); 480 theDate = d; 481 if (! nameHasBeenSet) { 482 instrumentName = ncfile.findGlobalAttribute("instrument"); 483 setName(instrumentName.getStringValue() + " " + sdfOut.format(d)); 484 nameHasBeenSet = true; 485 } 486 } 487 } 488 } 489 } catch (Exception e) { 490 logger.warn("Exception during processing of file: " + fileAbsPath); 491 throw (e); 492 } finally { 493 ncfile.close(); 494 } 495 } 496 497 } 498 499 // build each union aggregation element 500 Iterator<String> iterator = geoProductIDs.iterator(); 501 for (int elementNum = 0; elementNum < granuleCount; elementNum++) { 502 503 String s = null; 504 505 // build an XML (NCML actually) representation of the union aggregation of these two files 506 Namespace ns = Namespace.getNamespace("http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2"); 507 Element root = new Element("netcdf", ns); 508 Document document = new Document(root); 509 510 Element agg = new Element("aggregation", ns); 511 agg.setAttribute("type", "union"); 512 513 // TJJ - Loop over filename map, could be several products that need to be aggregated 514 Set set = filenameMap.keySet(); 515 Iterator mapIter = set.iterator(); 516 while (mapIter.hasNext()) { 517 String key = (String) mapIter.next(); 518 List l = (List) filenameMap.get(key); 519 Element fData = new Element("netcdf", ns); 520 fData.setAttribute("location", (String) l.get(elementNum)); 521 agg.addContent(fData); 522 s = (String) l.get(elementNum); 523 } 524 525 String geoFilename = null; 526 Element fGeo = new Element("netcdf", ns);; 527 528 if (! isCombinedProduct) { 529 530 if (isNOAA) { 531 geoFilename = s.substring(0, 532 s.lastIndexOf(File.separatorChar) + 1); 533 // check if we have the whole file name or just the prefix 534 String geoProductID = iterator.next(); 535 if (geoProductID.endsWith("h5")) { 536 geoFilename += geoProductID; 537 } else { 538 geoFilename += geoProductID; 539 geoFilename += s.substring(s 540 .lastIndexOf(File.separatorChar) + 6); 541 } 542 // Be sure file as specified by N_GEO_Ref global attribute really is there. 543 File tmpGeo = new File(geoFilename); 544 if (!tmpGeo.exists()) { 545 // Ok, the expected file defined (supposedly) exactly by a global att is not there... 546 // We need to check for similar geo files with different creation dates 547 String geoFileRelative = geoFilename 548 .substring(geoFilename 549 .lastIndexOf(File.separatorChar) + 1); 550 // also check for Terrain Corrected version of geo 551 String geoTerrainCorrected = geoFileRelative; 552 geoTerrainCorrected = geoTerrainCorrected.replace( 553 "OD", "TC"); 554 geoTerrainCorrected = geoTerrainCorrected.replace( 555 "MG", "TC"); 556 557 // now we make a file filter, and see if a matching geo file is present 558 File fList = new File( 559 geoFilename.substring( 560 0, 561 geoFilename 562 .lastIndexOf(File.separatorChar) + 1)); // current directory 563 564 FilenameFilter geoFilter = new FilenameFilter() { 565 public boolean accept(File dir, String name) { 566 if (name.matches(JPSSUtilities.SUOMI_GEO_REGEX_NOAA)) { 567 return true; 568 } else { 569 return false; 570 } 571 } 572 }; 573 574 File[] files = fList.listFiles(geoFilter); 575 for (File file : files) { 576 if (file.isDirectory()) { 577 continue; 578 } 579 // get the file name for convenience 580 String fName = file.getName(); 581 // is it one of the standard Ellipsoid geo types we are looking for? 582 if (fName.substring(0, 5).equals( 583 geoFileRelative.substring(0, 5))) { 584 int geoStartIdx = geoFileRelative 585 .indexOf("_d"); 586 int prdStartIdx = fName.indexOf("_d"); 587 String s1 = geoFileRelative.substring( 588 geoStartIdx, geoStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX); 589 String s2 = fName.substring(prdStartIdx, 590 prdStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX); 591 if (s1.equals(s2)) { 592 geoFilename = s 593 .substring( 594 0, 595 s.lastIndexOf(File.separatorChar) + 1) 596 + fName; 597 break; 598 } 599 } 600 // same check, but for Terrain Corrected version 601 if (fName.substring(0, 5).equals( 602 geoTerrainCorrected.substring(0, 5))) { 603 int geoStartIdx = geoTerrainCorrected 604 .indexOf("_d"); 605 int prdStartIdx = fName.indexOf("_d"); 606 String s1 = geoTerrainCorrected.substring( 607 geoStartIdx, geoStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX); 608 String s2 = fName.substring(prdStartIdx, 609 prdStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX); 610 if (s1.equals(s2)) { 611 geoFilename = s 612 .substring( 613 0, 614 s.lastIndexOf(File.separatorChar) + 1) 615 + fName; 616 break; 617 } 618 } 619 } 620 } 621 } else { 622 // NASA format 623 geoFilename = JPSSUtilities.replaceLast(s, "L1B", "GEO"); 624 // get list of files in current directory 625 File fList = 626 new File(geoFilename.substring(0, geoFilename.lastIndexOf(File.separatorChar) + 1)); 627 // make a NASA style file filter, and see if a matching geo file is present 628 FilenameFilter geoFilter = new FilenameFilter() { 629 public boolean accept(File dir, String name) { 630 if (name.matches(JPSSUtilities.SUOMI_GEO_REGEX_NASA)) { 631 return true; 632 } else { 633 return false; 634 } 635 } 636 }; 637 File[] files = fList.listFiles(geoFilter); 638 for (File file : files) { 639 if (file.isDirectory()) { 640 continue; 641 } 642 // get the file name for convenience 643 String fName = file.getName(); 644 String tmpStr = geoFilename.substring(s.lastIndexOf(File.separatorChar) + 1, 645 s.lastIndexOf(File.separatorChar) + (JPSSUtilities.NASA_CREATION_DATE_INDEX + 1)); 646 if (fName.substring(0, JPSSUtilities.NASA_CREATION_DATE_INDEX).equals(tmpStr.substring(0, JPSSUtilities.NASA_CREATION_DATE_INDEX))) { 647 geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1) + fName; 648 break; 649 } 650 } 651 } 652 logger.debug("Determined GEO file name should be: " + geoFilename); 653 fGeo.setAttribute("location", geoFilename); 654 // add this to list used if we create a zipped bundle 655 geoSources.add(geoFilename); 656 agg.addContent(fGeo); 657 } 658 659 root.addContent(agg); 660 XMLOutputter xmlOut = new XMLOutputter(); 661 String ncmlStr = xmlOut.outputString(document); 662 ByteArrayInputStream is = new ByteArrayInputStream(ncmlStr.getBytes()); 663 MultiDimensionReader netCDFReader = new NetCDFFile(is); 664 665 // let's try and look through the NetCDF reader and see what we can learn... 666 NetcdfFile ncdff = ((NetCDFFile) netCDFReader).getNetCDFFile(); 667 668 Group rg = ncdff.getRootGroup(); 669 // this is a list filled with unpacked qflag products, if any 670 ArrayList<VariableDS> qfProds = new ArrayList<VariableDS>(); 671 672 // this is a list filled with pseudo Brightness Temp variables converted from Radiance 673 ArrayList<VariableDS> btProds = new ArrayList<VariableDS>(); 674 675 List<Group> gl = rg.getGroups(); 676 if (gl != null) { 677 int xDimNASA = -1; 678 int yDimNASA = -1; 679 // Make a first pass to determine the shape of the geolocation data 680 for (Group g : gl) { 681 if (g.getFullName().contains("geolocation_data")) { 682 List<Variable> vl = g.getVariables(); 683 for (Variable v : vl) { 684 if (v.getShortName().equals("latitude")) { 685 // XXX TJJ Nov 2015 686 // Hack because fill value in attribute does not match 687 // what I am seeing in the data. 688 Attribute fillAtt = new Attribute("_FillValue", -999.0); 689 v.addAttribute(fillAtt); 690 pathToLat = v.getFullName(); 691 pathToProducts.add(v.getFullName()); 692 xDimNASA = v.getDimension(0).getLength(); 693 yDimNASA = v.getDimension(1).getLength(); 694 } 695 if (v.getShortName().equals("longitude")) { 696 // XXX TJJ Nov 2015 697 // Hack because fill value in attribute does not match 698 // what I am seeing in the data. 699 Attribute fillAtt = new Attribute("_FillValue", -999.0); 700 v.addAttribute(fillAtt); 701 pathToLon = v.getFullName(); 702 pathToProducts.add(v.getFullName()); 703 } 704 } 705 } 706 } 707 for (Group g : gl) { 708 logger.debug("Group name: " + g.getFullName()); 709 // NASA only - looking through observation_data and geolocation_data 710 if (g.getFullName().contains("observation_data")) { 711 List<Variable> vl = g.getVariables(); 712 for (Variable v : vl) { 713 // keep any data which matches geolocation dimensions 714 if (v.getDimension(0).getLength() == xDimNASA && 715 v.getDimension(1).getLength() == yDimNASA) { 716 logger.debug("Adding product: " + v.getFullName()); 717 pathToProducts.add(v.getFullName()); 718 719 Attribute aUnsigned = v.findAttribute("_Unsigned"); 720 if (aUnsigned != null) { 721 unsignedFlags.put(v.getFullName(), aUnsigned.getStringValue()); 722 } else { 723 unsignedFlags.put(v.getFullName(), "false"); 724 } 725 726 // store units in a map for later 727 Attribute unitAtt = v.findAttribute("units"); 728 if (unitAtt != null) { 729 unitsNASA.put(v.getShortName(), unitAtt.getStringValue()); 730 } else { 731 unitsNASA.put(v.getShortName(), "Unknown"); 732 } 733 734 // TJJ Feb 2016 - Create BT variables where applicable 735 if ((v.getShortName().matches("M12|M13|M14|M15|M16")) || 736 (v.getShortName().matches("I04|I05"))) { 737 738 // Get the LUT variable, load into primitive array 739 Variable lut = g.findVariable(v.getShortName() + "_brightness_temperature_lut"); 740 int [] lutShape = lut.getShape(); 741 logger.debug("Handling NASA LUT Variable, LUT size: " + lutShape[0]); 742 743 // pull out valid min, max - these will be used for our new VariableDS 744 Attribute aVMin = lut.findAttribute("valid_min"); 745 Attribute aVMax = lut.findAttribute("valid_max"); 746 Attribute fillAtt = lut.findAttribute("_FillValue"); 747 logger.debug("valid_min from LUT: " + aVMin.getNumericValue()); 748 logger.debug("valid_max from LUT: " + aVMax.getNumericValue()); 749 750 // A little hacky, but at this point the class is such a mess 751 // that what's a little more, right? Load M12-M16, I4-I5 LUTS 752 753 if (v.getShortName().matches("M12")) { 754 m12LUT = new float[lutShape[0]]; 755 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 756 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 757 m12LUT[lutIdx] = lutArray.get(lutIdx); 758 } 759 } 760 761 if (v.getShortName().matches("M13")) { 762 m13LUT = new float[lutShape[0]]; 763 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 764 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 765 m13LUT[lutIdx] = lutArray.get(lutIdx); 766 } 767 } 768 769 if (v.getShortName().matches("M14")) { 770 m14LUT = new float[lutShape[0]]; 771 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 772 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 773 m14LUT[lutIdx] = lutArray.get(lutIdx); 774 } 775 } 776 777 if (v.getShortName().matches("M15")) { 778 m15LUT = new float[lutShape[0]]; 779 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 780 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 781 m15LUT[lutIdx] = lutArray.get(lutIdx); 782 } 783 } 784 785 if (v.getShortName().matches("M16")) { 786 m16LUT = new float[lutShape[0]]; 787 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 788 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 789 m16LUT[lutIdx] = lutArray.get(lutIdx); 790 } 791 } 792 793 if (v.getShortName().matches("I04")) { 794 i04LUT = new float[lutShape[0]]; 795 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 796 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 797 i04LUT[lutIdx] = lutArray.get(lutIdx); 798 } 799 } 800 801 if (v.getShortName().matches("I05")) { 802 i05LUT = new float[lutShape[0]]; 803 ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read(); 804 for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) { 805 i05LUT[lutIdx] = lutArray.get(lutIdx); 806 } 807 } 808 809 // Create a pseudo-variable, fill using LUT 810 // make a copy of the source variable 811 // NOTE: by using a VariableDS here, the original 812 // variable is used for the I/O, this matters! 813 VariableDS vBT = new VariableDS(g, v, false); 814 815 // Name is orig name plus suffix 816 vBT.setShortName(v.getShortName() + "_BT"); 817 818 vBT.addAttribute(fillAtt); 819 vBT.addAttribute(aVMin); 820 vBT.addAttribute(aVMax); 821 822 if (v.getShortName().matches("M12")) { 823 lutMap.put(vBT.getFullName(), m12LUT); 824 } 825 if (v.getShortName().matches("M13")) { 826 lutMap.put(vBT.getFullName(), m13LUT); 827 } 828 if (v.getShortName().matches("M14")) { 829 lutMap.put(vBT.getFullName(), m14LUT); 830 } 831 if (v.getShortName().matches("M15")) { 832 lutMap.put(vBT.getFullName(), m15LUT); 833 } 834 if (v.getShortName().matches("M16")) { 835 lutMap.put(vBT.getFullName(), m16LUT); 836 } 837 if (v.getShortName().matches("I04")) { 838 lutMap.put(vBT.getFullName(), i04LUT); 839 } 840 if (v.getShortName().matches("I05")) { 841 lutMap.put(vBT.getFullName(), i05LUT); 842 } 843 pathToProducts.add(vBT.getFullName()); 844 btProds.add(vBT); 845 } 846 } 847 } 848 } 849 if (g.getFullName().contains("geolocation_data")) { 850 List<Variable> vl = g.getVariables(); 851 for (Variable v : vl) { 852 // keep any data which matches geolocation dimensions 853 if (v.getDimension(0).getLength() == xDimNASA && 854 v.getDimension(1).getLength() == yDimNASA) { 855 // except we already found Lat and Lon, skip those 856 if ((v.getShortName().equals("latitude")) || 857 (v.getShortName().equals("latitude"))) continue; 858 logger.debug("Adding product: " + v.getFullName()); 859 pathToProducts.add(v.getFullName()); 860 } 861 } 862 } 863 864 // NOAA only - we are looking through All_Data, finding displayable data 865 if (g.getFullName().contains("All_Data")) { 866 List<Group> adg = g.getGroups(); 867 int xDim = -1; 868 int yDim = -1; 869 870 // two sub-iterations, first one to find geolocation and product dimensions 871 for (Group subG : adg) { 872 logger.debug("Sub group name: " + subG.getFullName()); 873 String subName = subG.getFullName(); 874 if (subName.contains("-GEO")) { 875 // this is the geolocation data 876 String geoBaseName = subG.getShortName(); 877 geoBaseName = geoBaseName.substring(0, geoBaseName.indexOf('_')); 878 if (! haveGeoMetaData) { 879 String geoProfileFileName = nppPP.getProfileFileName(geoBaseName); 880 // also add meta data from geolocation profile 881 nppPP.addMetaDataFromFile(geoProfileFileName); 882 haveGeoMetaData = true; 883 } 884 List<Variable> vl = subG.getVariables(); 885 for (Variable v : vl) { 886 if (v.getFullName().endsWith(SEPARATOR_CHAR + "Latitude")) { 887 pathToLat = v.getFullName(); 888 logger.debug("Ellipsoid Lat/Lon Variable: " + v.getFullName()); 889 // get the dimensions of the lat variable 890 Dimension dAlongTrack = v.getDimension(0); 891 yDim = dAlongTrack.getLength(); 892 Dimension dAcrossTrack = v.getDimension(1); 893 xDim = dAcrossTrack.getLength(); 894 logger.debug("Lat across track dim: " + dAcrossTrack.getLength()); 895 } 896 if (v.getFullName().endsWith(SEPARATOR_CHAR + "Longitude")) { 897 // we got dimensions from lat, don't need 'em twice, but need path 898 pathToLon = v.getFullName(); 899 } 900 } 901 // one more pass in case there is terrain-corrected Lat/Lon 902 for (Variable v : vl) { 903 if (v.getFullName().endsWith(SEPARATOR_CHAR + "Latitude_TC")) { 904 pathToLat = v.getFullName(); 905 logger.debug("Switched Lat/Lon Variable to TC: " + v.getFullName()); 906 // get the dimensions of the lat variable 907 Dimension dAlongTrack = v.getDimension(0); 908 yDim = dAlongTrack.getLength(); 909 Dimension dAcrossTrack = v.getDimension(1); 910 xDim = dAcrossTrack.getLength(); 911 logger.debug("Lat across track dim: " + dAcrossTrack.getLength()); 912 } 913 if (v.getFullName().endsWith(SEPARATOR_CHAR + "Longitude_TC")) { 914 // we got dimensions from lat, don't need 'em twice, but need path 915 pathToLon = v.getFullName(); 916 } 917 } 918 } 919 } 920 921 // second to identify displayable products 922 for (Group subG : adg) { 923 // this is the product data 924 List<Variable> vl = subG.getVariables(); 925 for (Variable v : vl) { 926 boolean useThis = false; 927 String vName = v.getFullName(); 928 logger.trace("Variable: " + vName); 929 String varShortName = vName.substring(vName.lastIndexOf(SEPARATOR_CHAR) + 1); 930 931 // Special code to handle quality flags. We throw out anything 932 // that does not match bounds of the geolocation data 933 934 if (varShortName.startsWith("QF")) { 935 936 logger.trace("Handling Quality Flag: " + varShortName); 937 938 // this check is done later for ALL variables, but we need 939 // it early here to weed out those quality flags that are 940 // simply a small set of data w/no granule geo nbounds 941 boolean xScanOk = false; 942 boolean yScanOk = false; 943 List<Dimension> dl = v.getDimensions(); 944 945 // toss out > 2D Quality Flags 946 if (dl.size() > 2) { 947 logger.trace("SKIPPING QF, > 2D: " + varShortName); 948 continue; 949 } 950 951 for (Dimension d : dl) { 952 // in order to consider this a displayable product, make sure 953 // both scan direction dimensions are present and look like a granule 954 if (d.getLength() == xDim) { 955 xScanOk = true; 956 } 957 if (d.getLength() == yDim) { 958 yScanOk = true; 959 } 960 } 961 962 if (! (xScanOk && yScanOk)) { 963 logger.trace("SKIPPING QF, does not match geo bounds: " + varShortName); 964 continue; 965 } 966 967 ArrayList<QualityFlag> qfal = nppPP.getQualityFlags(varShortName); 968 if (qfal != null) { 969 for (QualityFlag qf : qfal) { 970 qf.setPackedName(vName); 971 // make a copy of the qflag variable 972 // NOTE: by using a VariableDS here, the original 973 // variable is used for the I/O, this matters! 974 VariableDS vqf = new VariableDS(subG, v, false); 975 // prefix with QF num to help guarantee uniqueness across groups 976 // this will cover most cases, but could still be dupe names 977 // within a single QF. This is handled when fetching XMLPP metadata 978 vqf.setShortName( 979 varShortName.substring(0, 3) + "_" + qf.getName() 980 ); 981 logger.debug("New QF var full name: " + vqf.getFullName()); 982 qfProds.add(vqf); 983 qfMap.put(vqf.getFullName(), qf); 984 } 985 } 986 } 987 988 // for CrIS instrument, first find dimensions of var matching 989 // CrIS filter, then throw out all variables which don't match 990 // those dimensions 991 992 if (instrumentName.getStringValue().equals("CrIS")) { 993 if (! vName.contains("GEO")) { 994 if (! varShortName.startsWith(crisFilter)) { 995 logger.trace("Skipping variable: " + varShortName); 996 continue; 997 } 998 } else { 999 // these variables are all GEO-related 1000 // if they match lat/lon bounds, keep them 1001 List<Dimension> dl = v.getDimensions(); 1002 if (dl.size() == 3) { 1003 boolean isDisplayableCrIS = true; 1004 for (Dimension d : dl) { 1005 if ((d.getLength() != xDim) && (d.getLength() != yDim) && (d.getLength() != 9)) { 1006 isDisplayableCrIS = false; 1007 } 1008 } 1009 if (! isDisplayableCrIS) { 1010 continue; 1011 } 1012 } 1013 } 1014 } 1015 1016 // for OMPS, only Radiance for now... 1017 if (instrumentName.getStringValue().contains("OMPS")) { 1018 if (! varShortName.startsWith(ompsFilter)) { 1019 logger.trace("Skipping OMPS variable: " + varShortName); 1020 continue; 1021 } 1022 } 1023 1024 DataType dt = v.getDataType(); 1025 if ((dt.getSize() != 4) && (dt.getSize() != 2) && (dt.getSize() != 1)) { 1026 continue; 1027 } 1028 1029 List<Dimension> dl = v.getDimensions(); 1030 if (dl.size() > 4) { 1031 continue; 1032 } 1033 1034 // for now, skip any 3D VIIRS data 1035 if (instrumentName.getStringValue().equals("VIIRS")) { 1036 if (dl.size() == 3) { 1037 continue; 1038 } 1039 } 1040 1041 boolean xScanOk = false; 1042 boolean yScanOk = false; 1043 for (Dimension d : dl) { 1044 // in order to consider this a displayable product, make sure 1045 // both scan direction dimensions are present and look like a granule 1046 if (d.getLength() == xDim) { 1047 xScanOk = true; 1048 } 1049 if (d.getLength() == yDim) { 1050 yScanOk = true; 1051 } 1052 } 1053 1054 if (xScanOk && yScanOk) { 1055 useThis = true; 1056 } 1057 1058 // For ATMS, only 3-D variable we pass through is BrightnessTemperature 1059 // Dimensions for BT are (lon, lat, channel) 1060 if (instrumentName.getStringValue().equals("ATMS")) { 1061 if (dl.size() == 3) { 1062 boolean isDisplayableATMS = false; 1063 for (Dimension d : dl) { 1064 if (d.getLength() == JPSSUtilities.ATMSChannelCenterFrequencies.length) { 1065 isDisplayableATMS = true; 1066 logger.trace("This variable has a dimension matching num ATMS channels"); 1067 break; 1068 } 1069 } 1070 if (! isDisplayableATMS) useThis = false; 1071 } 1072 } 1073 1074 // sensor data with a channel dimension 1075 if (useThis) { 1076 if ((instrumentName.getStringValue().equals("CrIS")) || 1077 (instrumentName.getStringValue().equals("ATMS")) || 1078 (instrumentName.getStringValue().contains("OMPS"))) { 1079 isVIIRS = false; 1080 logger.debug("Handling non-VIIRS data source..."); 1081 } 1082 } 1083 1084 if (useThis) { 1085 // loop through the variable list again, looking for a corresponding "Factors" 1086 float scaleVal = 1f; 1087 float offsetVal = 0f; 1088 boolean unpackFlag = false; 1089 1090 // if the granule has an entry for this variable name 1091 // get the data, data1 = scale, data2 = offset 1092 // create and poke attributes with this data 1093 // endif 1094 1095 String factorsVarName = nppPP.getScaleFactorName(varShortName); 1096 logger.debug("Mapping: " + varShortName + " to: " + factorsVarName); 1097 if (factorsVarName != null) { 1098 for (Variable fV : vl) { 1099 if (fV.getShortName().equals(factorsVarName)) { 1100 logger.trace("Pulling scale and offset values from variable: " + fV.getShortName()); 1101 ucar.ma2.Array a = fV.read(); 1102 float[] so = (float[]) a.copyTo1DJavaArray(); 1103 scaleVal = so[0]; 1104 offsetVal = so[1]; 1105 logger.trace("Scale value: " + scaleVal + ", Offset value: " + offsetVal); 1106 unpackFlag = true; 1107 break; 1108 } 1109 } 1110 } 1111 1112 // poke in scale/offset attributes for now 1113 1114 Attribute a1 = new Attribute("scale_factor", scaleVal); 1115 v.addAttribute(a1); 1116 Attribute a2 = new Attribute("add_offset", offsetVal); 1117 v.addAttribute(a2); 1118 1119 // add valid range and fill value attributes here 1120 // try to fill in valid range 1121 if (nppPP.hasNameAndMetaData(varShortName)) { 1122 String rangeMin = nppPP.getRangeMin(varShortName); 1123 String rangeMax = nppPP.getRangeMax(varShortName); 1124 logger.trace("range min: " + rangeMin + ", range max: " + rangeMax); 1125 // only store range attribute if VALID range found 1126 if ((rangeMin != null) && (rangeMax != null)) { 1127 int [] shapeArr = new int [] { 2 }; 1128 ArrayFloat af = new ArrayFloat(shapeArr); 1129 try { 1130 af.setFloat(0, Float.parseFloat(rangeMin)); 1131 } catch (NumberFormatException nfe) { 1132 af.setFloat(0, new Float(Integer.MIN_VALUE)); 1133 } 1134 try { 1135 af.setFloat(1, Float.parseFloat(rangeMax)); 1136 } catch (NumberFormatException nfe) { 1137 af.setFloat(1, new Float(Integer.MAX_VALUE)); 1138 } 1139 Attribute rangeAtt = new Attribute("valid_range", af); 1140 v.addAttribute(rangeAtt); 1141 } 1142 1143 // check for and load fill values too... 1144 1145 // we need to check two places, first, the XML product profile 1146 ArrayList<Float> fval = nppPP.getFillValues(varShortName); 1147 1148 // 2nd, does the variable already have one defined? 1149 // if there was already a fill value associated with this variable, make 1150 // sure we bring that along for the ride too... 1151 Attribute aFill = v.findAttribute("_FillValue"); 1152 1153 // determine size of our fill value array 1154 int fvArraySize = 0; 1155 if (aFill != null) fvArraySize++; 1156 if (! fval.isEmpty()) fvArraySize += fval.size(); 1157 int [] fillShape = new int [] { fvArraySize }; 1158 1159 // allocate the array 1160 ArrayFloat afFill = new ArrayFloat(fillShape); 1161 1162 // and FINALLY, fill it! 1163 if (! fval.isEmpty()) { 1164 for (int fillIdx = 0; fillIdx < fval.size(); fillIdx++) { 1165 afFill.setFloat(fillIdx, fval.get(fillIdx)); 1166 logger.trace("Adding fill value (from XML): " + fval.get(fillIdx)); 1167 } 1168 } 1169 1170 if (aFill != null) { 1171 Number n = aFill.getNumericValue(); 1172 // is the data unsigned? 1173 Attribute aUnsigned = v.findAttribute("_Unsigned"); 1174 float fillValAsFloat = Float.NaN; 1175 if (aUnsigned != null) { 1176 if (aUnsigned.getStringValue().equals("true")) { 1177 DataType fvdt = aFill.getDataType(); 1178 logger.trace("Data String: " + aFill.toString()); 1179 logger.trace("DataType primitive type: " + fvdt.getPrimitiveClassType()); 1180 // signed byte that needs conversion? 1181 if (fvdt.getPrimitiveClassType() == byte.class) { 1182 fillValAsFloat = (float) Util.unsignedByteToInt(n.byteValue()); 1183 } 1184 else if (fvdt.getPrimitiveClassType() == short.class) { 1185 fillValAsFloat = (float) Util.unsignedShortToInt(n.shortValue()); 1186 } else { 1187 fillValAsFloat = n.floatValue(); 1188 } 1189 } 1190 } 1191 afFill.setFloat(fvArraySize - 1, fillValAsFloat); 1192 logger.trace("Adding fill value (from variable): " + fillValAsFloat); 1193 } 1194 Attribute fillAtt = new Attribute("_FillValue", afFill); 1195 v.addAttribute(fillAtt); 1196 } 1197 1198 Attribute aUnsigned = v.findAttribute("_Unsigned"); 1199 if (aUnsigned != null) { 1200 unsignedFlags.put(v.getFullName(), aUnsigned.getStringValue()); 1201 } else { 1202 unsignedFlags.put(v.getFullName(), "false"); 1203 } 1204 1205 if (unpackFlag) { 1206 unpackFlags.put(v.getFullName(), "true"); 1207 } else { 1208 unpackFlags.put(v.getFullName(), "false"); 1209 } 1210 1211 logger.debug("Adding product: " + v.getFullName()); 1212 pathToProducts.add(v.getFullName()); 1213 1214 } 1215 } 1216 } 1217 } 1218 } 1219 } 1220 1221 // add in any unpacked qflag products 1222 for (VariableDS qfV: qfProds) { 1223 // skip the spares - they are reserved for future use 1224 if (qfV.getFullName().endsWith("Spare")) { 1225 continue; 1226 } 1227 // String.endsWith is case sensitive so gotta check both cases 1228 if (qfV.getFullName().endsWith("spare")) { 1229 continue; 1230 } 1231 ncdff.addVariable(qfV.getGroup(), qfV); 1232 logger.trace("Adding QF product: " + qfV.getFullName()); 1233 pathToProducts.add(qfV.getFullName()); 1234 unsignedFlags.put(qfV.getFullName(), "true"); 1235 unpackFlags.put(qfV.getFullName(), "false"); 1236 } 1237 1238 // add in any pseudo BT products from NASA data 1239 for (Variable vBT: btProds) { 1240 logger.trace("Adding BT product: " + vBT.getFullName()); 1241 ncdff.addVariable(vBT.getGroup(), vBT); 1242 unsignedFlags.put(vBT.getFullName(), "true"); 1243 unpackFlags.put(vBT.getFullName(), "false"); 1244 } 1245 1246 ncdfal.add((NetCDFFile) netCDFReader); 1247 } 1248 1249 } catch (Exception e) { 1250 logger.error("cannot create NetCDF reader for files selected", e); 1251 if (e.getMessage() != null && e.getMessage().equals("XML Product Profile Error")) { 1252 throw new VisADException("Unable to extract metadata from required XML Product Profile", e); 1253 } 1254 } 1255 1256 // initialize the aggregation reader object 1257 try { 1258 if (isNOAA) { 1259 nppAggReader = new GranuleAggregation(ncdfal, pathToProducts, "Track", "XTrack", isVIIRS); 1260 ((GranuleAggregation) nppAggReader).setQfMap(qfMap); 1261 } else { 1262 nppAggReader = new GranuleAggregation(ncdfal, pathToProducts, "number_of_lines", "number_of_pixels", isVIIRS); 1263 ((GranuleAggregation) nppAggReader).setLUTMap(lutMap); 1264 } 1265 } catch (Exception e) { 1266 throw new VisADException("Unable to initialize aggregation reader", e); 1267 } 1268 1269 // make sure we found valid data 1270 if (pathToProducts.size() == 0) { 1271 throw new VisADException("No data found in files selected"); 1272 } 1273 1274 logger.debug("Number of adapters needed: " + pathToProducts.size()); 1275 adapters = new MultiDimensionAdapter[pathToProducts.size()]; 1276 Hashtable<String, String[]> properties = new Hashtable<>(); 1277 1278 Iterator<String> iterator = pathToProducts.iterator(); 1279 int pIdx = 0; 1280 boolean adapterCreated = false; 1281 while (iterator.hasNext()) { 1282 String pStr = iterator.next(); 1283 logger.debug("Working on adapter number " + (pIdx + 1) + ": " + pStr); 1284 Map<String, Object> swathTable = SwathAdapter.getEmptyMetadataTable(); 1285 Map<String, Object> spectTable = SpectrumAdapter.getEmptyMetadataTable(); 1286 swathTable.put("array_name", pStr); 1287 swathTable.put("lon_array_name", pathToLon); 1288 swathTable.put("lat_array_name", pathToLat); 1289 swathTable.put("XTrack", "XTrack"); 1290 swathTable.put("Track", "Track"); 1291 swathTable.put("geo_Track", "Track"); 1292 swathTable.put("geo_XTrack", "XTrack"); 1293 // TJJ is this even needed? Is product_name used anywhere? 1294 if (productName == null) productName = pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1); 1295 swathTable.put("product_name", productName); 1296 1297 // array_name common to spectrum table 1298 spectTable.put("array_name", pStr); 1299 spectTable.put("product_name", productName); 1300 1301 if (! isVIIRS) { 1302 1303 // 3D data is either ATMS, OMPS, or CrIS 1304 if ((instrumentName.getShortName() != null) && (instrumentName.getStringValue().equals("ATMS"))) { 1305 1306 spectTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 1307 swathTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 1308 1309 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 1310 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 1311 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 1312 spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 1313 spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 1314 spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 1315 1316 spectTable.put(SpectrumAdapter.channelType, "wavelength"); 1317 spectTable.put(SpectrumAdapter.channels_name, "Channel"); 1318 spectTable.put(SpectrumAdapter.x_dim_name, "XTrack"); 1319 spectTable.put(SpectrumAdapter.y_dim_name, "Track"); 1320 1321 int numChannels = JPSSUtilities.ATMSChannelCenterFrequencies.length; 1322 float[] bandArray = new float[numChannels]; 1323 String[] bandNames = new String[numChannels]; 1324 for (int bIdx = 0; bIdx < numChannels; bIdx++) { 1325 bandArray[bIdx] = JPSSUtilities.ATMSChannelCenterFrequencies[bIdx]; 1326 bandNames[bIdx] = "Channel " + (bIdx + 1); 1327 } 1328 spectTable.put(SpectrumAdapter.channelValues, bandArray); 1329 spectTable.put(SpectrumAdapter.bandNames, bandNames); 1330 1331 } else { 1332 if (instrumentName.getStringValue().equals("CrIS")) { 1333 1334 swathTable.put("XTrack", "dim1"); 1335 swathTable.put("Track", "dim0"); 1336 swathTable.put("geo_XTrack", "dim1"); 1337 swathTable.put("geo_Track", "dim0"); 1338 swathTable.put("product_name", "CrIS_SDR"); 1339 swathTable.put(SpectrumAdapter.channelIndex_name, "dim3"); 1340 swathTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 1341 1342 spectTable.put(SpectrumAdapter.channelIndex_name, "dim3"); 1343 spectTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 1344 spectTable.put(SpectrumAdapter.x_dim_name, "dim1"); 1345 spectTable.put(SpectrumAdapter.y_dim_name, "dim0"); 1346 1347 } else if (instrumentName.getStringValue().contains("OMPS")) { 1348 1349 spectTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 1350 swathTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 1351 1352 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 1353 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 1354 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 1355 spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 1356 spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 1357 spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 1358 1359 spectTable.put(SpectrumAdapter.channelType, "wavelength"); 1360 spectTable.put(SpectrumAdapter.channels_name, "Channel"); 1361 spectTable.put(SpectrumAdapter.x_dim_name, "XTrack"); 1362 spectTable.put(SpectrumAdapter.y_dim_name, "Track"); 1363 1364 int numChannels = 200; 1365 if (instrumentName.getStringValue().equals("OMPS-TC")) { 1366 numChannels = 260; 1367 } 1368 logger.debug("Setting up OMPS adapter, num channels: " + numChannels); 1369 float[] bandArray = new float[numChannels]; 1370 String[] bandNames = new String[numChannels]; 1371 for (int bIdx = 0; bIdx < numChannels; bIdx++) { 1372 bandArray[bIdx] = bIdx; 1373 bandNames[bIdx] = "Channel " + (bIdx + 1); 1374 } 1375 spectTable.put(SpectrumAdapter.channelValues, bandArray); 1376 spectTable.put(SpectrumAdapter.bandNames, bandNames); 1377 1378 } else { 1379 // sorry, if we can't id the instrument, we can't display the data! 1380 throw new VisADException("Unable to determine instrument name"); 1381 } 1382 } 1383 1384 } else { 1385 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack"}); 1386 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 1387 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 1388 } 1389 1390 swathTable.put("scale_name", "scale_factor"); 1391 swathTable.put("offset_name", "add_offset"); 1392 swathTable.put("fill_value_name", "_FillValue"); 1393 swathTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1)); 1394 spectTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1)); 1395 1396 // set the valid range hash if data is available 1397 if (nppPP != null) { 1398 if (nppPP.getRangeMin(pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1)) != null) { 1399 swathTable.put("valid_range", "valid_range"); 1400 } 1401 } 1402 1403 String unsignedAttributeStr = unsignedFlags.get(pStr); 1404 if ((unsignedAttributeStr != null) && (unsignedAttributeStr.equals("true"))) { 1405 swathTable.put("unsigned", unsignedAttributeStr); 1406 } 1407 1408 String unpackFlagStr = unpackFlags.get(pStr); 1409 if ((unpackFlagStr != null) && (unpackFlagStr.equals("true"))) { 1410 swathTable.put("unpack", "true"); 1411 } 1412 1413 // For Suomi NPP data, do valid range check AFTER applying scale/offset 1414 swathTable.put("range_check_after_scaling", "true"); 1415 1416 // pass in a GranuleAggregation reader... 1417 if (! isVIIRS) { 1418 if (instrumentName.getStringValue().equals("ATMS")) { 1419 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 1420 adapterCreated = true; 1421 SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable); 1422 DataCategory.createCategory("MultiSpectral"); 1423 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 1424 MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 1425 "BrightnessTemperature", "BrightnessTemperature", "SuomiNPP", "ATMS"); 1426 msd.setInitialWavenumber(JPSSUtilities.ATMSChannelCenterFrequencies[0]); 1427 multiSpectralData.add(msd); 1428 } 1429 if (instrumentName.getStringValue().equals("CrIS")) { 1430 if (pStr.contains(crisFilter)) { 1431 adapters[pIdx] = new CrIS_SDR_SwathAdapter(nppAggReader, swathTable); 1432 adapterCreated = true; 1433 CrIS_SDR_Spectrum csa = new CrIS_SDR_Spectrum(nppAggReader, spectTable); 1434 DataCategory.createCategory("MultiSpectral"); 1435 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 1436 MultiSpectralData msd = new CrIS_SDR_MultiSpectralData((CrIS_SDR_SwathAdapter) adapters[pIdx], csa); 1437 msd.setInitialWavenumber(csa.getInitialWavenumber()); 1438 msd_CrIS.add(msd); 1439 } 1440 } 1441 if (instrumentName.getStringValue().contains("OMPS")) { 1442 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 1443 adapterCreated = true; 1444 SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable); 1445 DataCategory.createCategory("MultiSpectral"); 1446 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 1447 MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 1448 "RadianceEarth", "RadianceEarth", "SuomiNPP", "OMPS"); 1449 msd.setInitialWavenumber(0); 1450 multiSpectralData.add(msd); 1451 } 1452 if (pIdx == 0) { 1453 // generate default subset for ATMS and OMPS 1454 if (! instrumentName.getStringValue().equals("CrIS")) { 1455 defaultSubset = multiSpectralData.get(pIdx).getDefaultSubset(); 1456 } 1457 } 1458 1459 } else { 1460 // setting NOAA-format units 1461 String varName = pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1); 1462 String varShortName = pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1); 1463 String units = nppPP.getUnits(varShortName); 1464 1465 // setting NASA-format units 1466 if (! isNOAA) { 1467 units = unitsNASA.get(varShortName); 1468 // Need to set _BT variables manually, since they are created on the fly 1469 if (varShortName.endsWith("_BT")) units = "Kelvin"; 1470 } 1471 if (units == null) units = "Unknown"; 1472 Unit u = null; 1473 try { 1474 u = Parser.parse(units); 1475 } catch (NoSuchUnitException e) { 1476 u = new DerivedUnit(units); 1477 logger.debug("Unknown units: " + units); 1478 } catch (ParseException e) { 1479 u = new DerivedUnit(units); 1480 logger.debug("Unparseable units: " + units); 1481 } 1482 // associate this variable with these units, if not done already 1483 RealType.getRealType(varName, u); 1484 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 1485 adapterCreated = true; 1486 if (pIdx == 0) { 1487 defaultSubset = adapters[pIdx].getDefaultSubset(); 1488 } 1489 categories = DataCategory.parseCategories("IMAGE"); 1490 } 1491 // only increment count if we created an adapter, some products are skipped 1492 if (adapterCreated) pIdx++; 1493 adapterCreated = false; 1494 } 1495 1496 if (msd_CrIS.size() > 0) { 1497 try { 1498 MultiSpectralAggr aggr = new MultiSpectralAggr(msd_CrIS.toArray(new MultiSpectralData[msd_CrIS.size()])); 1499 aggr.setInitialWavenumber(902.25f); 1500 multiSpectralData.add(aggr); 1501 defaultSubset = ((MultiSpectralData) msd_CrIS.get(0)).getDefaultSubset(); 1502 } catch (Exception e) { 1503 logger.error("Exception: ", e); 1504 } 1505 } 1506 1507 // Merge with pre-set properties 1508 Hashtable tmpHt = getProperties(); 1509 tmpHt.putAll(properties); 1510 setProperties(tmpHt); 1511 } 1512 1513 public void initAfterUnpersistence() { 1514 try { 1515 String zidvPath = 1516 McIDASV.getStaticMcv().getStateManager(). 1517 getProperty(IdvPersistenceManager.PROP_ZIDVPATH, ""); 1518 if (getTmpPaths() != null) { 1519 // New code for zipped bundles- 1520 // we want 'sources' to point to wherever the zipped data was unpacked. 1521 sources.clear(); 1522 // following PersistenceManager.fixBulkDataSources, get temporary data location 1523 for (Object o : getTmpPaths()) { 1524 String tempPath = (String) o; 1525 // replace macro string with actual path 1526 String expandedPath = tempPath.replace(PersistenceManager.MACRO_ZIDVPATH, zidvPath); 1527 // we don't want to add nav files to this list!: 1528 File f = new File(expandedPath); 1529 if (!f.getName().matches(JPSSUtilities.SUOMI_GEO_REGEX_NOAA)) { 1530 sources.add(expandedPath); 1531 } 1532 } 1533 1534 // mjh fix absolute paths in filenameMap 1535 logger.debug("original filenameMap: {}", filenameMap); 1536 Iterator keyIterator = filenameMap.keySet().iterator(); 1537 while (keyIterator.hasNext()) { 1538 String keyStr = (String) keyIterator.next(); 1539 List<String> fileNames = (List<String>) filenameMap.get(keyStr); 1540 for (int i = 0; i < fileNames.size(); i++) { 1541 String name = fileNames.get(i); 1542 int lastSeparator = name.lastIndexOf(File.separatorChar); 1543 String sub = name.substring(0, lastSeparator); 1544 name = name.replace(sub, zidvPath); 1545 fileNames.set(i, name); 1546 } 1547 } 1548 logger.debug("filenameMap with zidvPath: {}", filenameMap); 1549 } else { 1550 // leave in original unpersistence code - this will get run for unzipped bundles. 1551 // TODO: do we need to handle the "Save with relative paths" case specially? 1552 if (! oldSources.isEmpty()) { 1553 sources.clear(); 1554 for (Object o : oldSources) { 1555 sources.add((String) o); 1556 } 1557 } 1558 } 1559 oldSources.clear(); 1560 setup(); 1561 } catch (Exception e) { 1562 logger.error("Exception: ", e); 1563 } 1564 } 1565 1566 /* (non-Javadoc) 1567 * @see edu.wisc.ssec.mcidasv.data.HydraDataSource#canSaveDataToLocalDisk() 1568 */ 1569 @Override 1570 public boolean canSaveDataToLocalDisk() { 1571 // At present, Suomi data is always data granules on disk 1572 return true; 1573 } 1574 1575 /* (non-Javadoc) 1576 * @see ucar.unidata.data.DataSourceImpl#saveDataToLocalDisk(java.lang.String, java.lang.Object, boolean) 1577 */ 1578 @Override 1579 protected List saveDataToLocalDisk(String filePrefix, Object loadId, 1580 boolean changeLinks) throws Exception { 1581 // need to make a list of all data granule files 1582 // PLUS all geolocation granule files, but only if accessed separate! 1583 List<String> fileList = new ArrayList<String>(); 1584 for (Object o : sources) { 1585 fileList.add((String) o); 1586 } 1587 for (String s : geoSources) { 1588 fileList.add(s); 1589 } 1590 return fileList; 1591 } 1592 1593 public List<String> getOldSources() { 1594 return oldSources; 1595 } 1596 1597 public void setOldSources(List<String> oldSources) { 1598 this.oldSources = oldSources; 1599 } 1600 1601 public Map<String, List<String>> getFilenameMap() { 1602 return filenameMap; 1603 } 1604 1605 public void setFilenameMap(Map<String, List<String>> filenameMap) { 1606 this.filenameMap = filenameMap; 1607 } 1608 1609 /** 1610 * Make and insert the {@link DataChoice DataChoices} for this 1611 * {@code DataSource}. 1612 */ 1613 1614 public void doMakeDataChoices() { 1615 1616 // special loop for CrIS, ATMS, and OMPS data 1617 if (multiSpectralData.size() > 0) { 1618 for (int k = 0; k < multiSpectralData.size(); k++) { 1619 MultiSpectralData adapter = multiSpectralData.get(k); 1620 DataChoice choice = null; 1621 try { 1622 choice = doMakeDataChoice(k, adapter); 1623 choice.setObjectProperty(Constants.PROP_GRANULE_COUNT, 1624 getProperty(Constants.PROP_GRANULE_COUNT, "1 Granule")); 1625 msdMap.put(choice.getName(), adapter); 1626 addDataChoice(choice); 1627 } catch (Exception e) { 1628 logger.error("Exception: ", e); 1629 } 1630 } 1631 return; 1632 } 1633 1634 // all other data (VIIRS and 2D EDRs) 1635 if (adapters != null) { 1636 for (int idx = 0; idx < adapters.length; idx++) { 1637 DataChoice choice = null; 1638 try { 1639 choice = doMakeDataChoice(idx, adapters[idx].getArrayName()); 1640 choice.setObjectProperty(Constants.PROP_GRANULE_COUNT, 1641 getProperty(Constants.PROP_GRANULE_COUNT, "1 Granule")); 1642 } 1643 catch (Exception e) { 1644 e.printStackTrace(); 1645 logger.error("doMakeDataChoice failed"); 1646 } 1647 1648 if (choice != null) { 1649 addDataChoice(choice); 1650 } 1651 } 1652 } 1653 } 1654 1655 private DataChoice doMakeDataChoice(int idx, String var) throws Exception { 1656 String name = var; 1657 DataSelection dataSel = new MultiDimensionSubset(defaultSubset); 1658 Hashtable subset = new Hashtable(); 1659 subset.put(new MultiDimensionSubset(), dataSel); 1660 // TJJ Hack check for uber-odd case of data type varies for same variable 1661 // If it's M12 - M16, it's a BrightnessTemperature, otherwise Reflectance 1662 if (name.endsWith("BrightnessTemperatureOrReflectance")) { 1663 name = name.substring(0, name.length() - "BrightnessTemperatureOrReflectance".length()); 1664 if (whichEDR.matches("M12|M13|M14|M15|M16")) { 1665 name = name + "BrightnessTemperature"; 1666 } else { 1667 name = name + "Reflectance"; 1668 } 1669 } 1670 DirectDataChoice ddc = new DirectDataChoice(this, idx, name, name, categories, subset); 1671 return ddc; 1672 } 1673 1674 private DataChoice doMakeDataChoice(int idx, MultiSpectralData adapter) throws Exception { 1675 String name = adapter.getName(); 1676 DataSelection dataSel = new MultiDimensionSubset(defaultSubset); 1677 Hashtable subset = new Hashtable(); 1678 subset.put(MultiDimensionSubset.key, dataSel); 1679 subset.put(MultiSpectralDataSource.paramKey, adapter.getParameter()); 1680 // TJJ Hack check for uber-odd case of data type varies for same variable 1681 // If it's M12 - M16, it's a BrightnessTemperature, otherwise Reflectance 1682 if (name.endsWith("BrightnessTemperatureOrReflectance")) { 1683 name = name.substring(0, name.length() - "BrightnessTemperatureOrReflectance".length()); 1684 if (whichEDR.matches("M12|M13|M14|M15|M16")) { 1685 name = name + "BrightnessTemperature"; 1686 } else { 1687 name = name + "Reflectance"; 1688 } 1689 } 1690 DirectDataChoice ddc = new DirectDataChoice(this, new Integer(idx), name, name, categories, subset); 1691 ddc.setProperties(subset); 1692 return ddc; 1693 } 1694 1695 /** 1696 * Check to see if this {@code SuomiNPPDataSource} is equal to the object 1697 * in question. 1698 * @param o object in question 1699 * @return true if they are the same or equivalent objects 1700 */ 1701 1702 public boolean equals(Object o) { 1703 if ( !(o instanceof SuomiNPPDataSource)) { 1704 return false; 1705 } 1706 return (this == (SuomiNPPDataSource) o); 1707 } 1708 1709 public MultiSpectralData getMultiSpectralData() { 1710 return multiSpectralData.get(0); 1711 } 1712 1713 public MultiSpectralData getMultiSpectralData(DataChoice choice) { 1714 return msdMap.get(choice.getName()); 1715 } 1716 1717 public String getDatasetName() { 1718 return filename; 1719 } 1720 1721 /** 1722 * @return the qfMap 1723 */ 1724 public Map<String, QualityFlag> getQfMap() { 1725 return qfMap; 1726 } 1727 1728 public void setDatasetName(String name) { 1729 filename = name; 1730 } 1731 1732 public Map<String, double[]> getSubsetFromLonLatRect(MultiDimensionSubset select, GeoSelection geoSelection) { 1733 GeoLocationInfo ginfo = geoSelection.getBoundingBox(); 1734 return adapters[0].getSubsetFromLonLatRect(select.getSubset(), ginfo.getMinLat(), ginfo.getMaxLat(), 1735 ginfo.getMinLon(), ginfo.getMaxLon()); 1736 } 1737 1738 public synchronized Data getData(DataChoice dataChoice, DataCategory category, 1739 DataSelection dataSelection, Hashtable requestProperties) 1740 throws VisADException, RemoteException { 1741 return this.getDataInner(dataChoice, category, dataSelection, requestProperties); 1742 } 1743 1744 1745 protected Data getDataInner(DataChoice dataChoice, DataCategory category, 1746 DataSelection dataSelection, Hashtable requestProperties) 1747 throws VisADException, RemoteException { 1748 1749 //- this hack keeps the HydraImageProbe from doing a getData() 1750 //- TODO: need to use categories? 1751 if (requestProperties != null) { 1752 if ((requestProperties.toString()).equals("{prop.requester=MultiSpectral}")) { 1753 return null; 1754 } 1755 } 1756 1757 GeoLocationInfo ginfo = null; 1758 GeoSelection geoSelection = null; 1759 1760 if ((dataSelection != null) && (dataSelection.getGeoSelection() != null)) { 1761 geoSelection = (dataSelection.getGeoSelection().getBoundingBox() != null) ? dataSelection.getGeoSelection() : 1762 dataChoice.getDataSelection().getGeoSelection(); 1763 } 1764 1765 if (geoSelection != null) { 1766 ginfo = geoSelection.getBoundingBox(); 1767 } 1768 1769 Data data = null; 1770 if (adapters == null) { 1771 return data; 1772 } 1773 1774 MultiDimensionAdapter adapter = null; 1775 1776 // pick the adapter with the same index as the current data choice 1777 int aIdx = 0; 1778 List<DataChoice> dcl = getDataChoices(); 1779 for (DataChoice dc : dcl) { 1780 if (dc.getName().equals(dataChoice.getName())) { 1781 aIdx = dcl.indexOf(dc); 1782 break; 1783 } 1784 } 1785 1786 adapter = adapters[aIdx]; 1787 1788 try { 1789 Map<String, double[]> subset = null; 1790 if (ginfo != null) { 1791 subset = adapter.getSubsetFromLonLatRect(ginfo.getMinLat(), ginfo.getMaxLat(), 1792 ginfo.getMinLon(), ginfo.getMaxLon(), 1793 geoSelection.getXStride(), 1794 geoSelection.getYStride(), 1795 geoSelection.getZStride()); 1796 } 1797 else { 1798 1799 MultiDimensionSubset select = null; 1800 Hashtable table = dataChoice.getProperties(); 1801 Enumeration keys = table.keys(); 1802 while (keys.hasMoreElements()) { 1803 Object key = keys.nextElement(); 1804 logger.debug("Key: " + key.toString()); 1805 if (key instanceof MultiDimensionSubset) { 1806 select = (MultiDimensionSubset) table.get(key); 1807 } 1808 } 1809 subset = select.getSubset(); 1810 logger.debug("Subset size: " + subset.size()); 1811 1812 if (dataSelection != null) { 1813 Hashtable props = dataSelection.getProperties(); 1814 if (props != null) { 1815 if (props.containsKey(SpectrumAdapter.channelIndex_name)) { 1816 logger.debug("Props contains channel index key..."); 1817 double[] coords = (double[]) subset.get(SpectrumAdapter.channelIndex_name); 1818 int idx = ((Integer) props.get(SpectrumAdapter.channelIndex_name)).intValue(); 1819 coords[0] = (double) idx; 1820 coords[1] = (double) idx; 1821 coords[2] = (double) 1; 1822 } 1823 } 1824 } 1825 } 1826 1827 if (subset != null) { 1828 data = adapter.getData(subset); 1829 data = applyProperties(data, requestProperties, subset, aIdx); 1830 } 1831 } catch (Exception e) { 1832 logger.error("getData Exception: ", e); 1833 } 1834 ////////// inq1429 return FieldImpl with time dim ///////////////// 1835 if (data != null) { 1836 List dateTimes = new ArrayList(); 1837 dateTimes.add(new DateTime(theDate)); 1838 SampledSet timeSet = (SampledSet) ucar.visad.Util.makeTimeSet(dateTimes); 1839 FunctionType ftype = new FunctionType(RealType.Time, data.getType()); 1840 FieldImpl fi = new FieldImpl(ftype, timeSet); 1841 fi.setSample(0, data); 1842 data = fi; 1843 } 1844 ////////////////////////////////////////////////////////////////// 1845 return data; 1846 } 1847 1848 protected Data applyProperties(Data data, Hashtable requestProperties, Map<String, double[]> subset, int adapterIndex) 1849 throws VisADException, RemoteException { 1850 Data new_data = data; 1851 1852 if (requestProperties == null) { 1853 new_data = data; 1854 return new_data; 1855 } 1856 1857 return new_data; 1858 } 1859 1860 protected void initDataSelectionComponents( 1861 List<DataSelectionComponent> components, 1862 final DataChoice dataChoice) { 1863 1864 try { 1865 // inq1429: need to handle FieldImpl here 1866 FieldImpl thing = (FieldImpl) dataChoice.getData(null); 1867 FlatField image; 1868 if (GridUtil.isTimeSequence(thing)) { 1869 image = (FlatField) thing.getSample(0); 1870 } else { 1871 image = (FlatField) thing; 1872 } 1873 if (image != null) { 1874 PreviewSelection ps = new PreviewSelection(dataChoice, image, null); 1875 // Region subsetting not yet implemented for CrIS data 1876 if (instrumentName.getStringValue().equals("CrIS")) { 1877 ps.enableSubsetting(false); 1878 } 1879 components.add(ps); 1880 } 1881 } catch (Exception e) { 1882 logger.error("Can't make PreviewSelection: ", e); 1883 } 1884 1885 } 1886 1887 /** 1888 * Add {@code Integer->String} translations to IDV's 1889 * {@literal "translations"} resource, so they will be made available to 1890 * the data probe of Image Display's. 1891 */ 1892 public void initQfTranslations() { 1893 1894 Map<String, Map<Integer, String>> translations = 1895 getIdv().getResourceManager(). 1896 getTranslationsHashtable(); 1897 1898 for (String qfKey : qfMap.keySet()) { 1899 // This string needs to match up with the data choice name: 1900 String qfKeySubstr = qfKey.replace("All_Data/", ""); 1901 // check if we've already added map for this QF 1902 if (!translations.containsKey(qfKeySubstr)) { 1903 Map<String, String> hm = qfMap.get(qfKey).getHm(); 1904 Map<Integer, String> newMap = 1905 new HashMap<Integer, String>(hm.size()); 1906 for (String dataValueKey : hm.keySet()) { 1907 // convert Map<String, String> to Map<Integer, String> 1908 Integer intKey = Integer.parseInt(dataValueKey); 1909 newMap.put(intKey, hm.get(dataValueKey)); 1910 } 1911 translations.put(qfKeySubstr, newMap); 1912 } 1913 } 1914 } 1915 1916}