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