001 /* 002 * $Id: SuomiNPPDataSource.java,v 1.4 2012/03/29 20:06:42 tommyj Exp $ 003 * 004 * This file is part of McIDAS-V 005 * 006 * Copyright 2007-2012 007 * Space Science and Engineering Center (SSEC) 008 * University of Wisconsin - Madison 009 * 1225 W. Dayton Street, Madison, WI 53706, USA 010 * https://www.ssec.wisc.edu/mcidas 011 * 012 * All Rights Reserved 013 * 014 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 015 * some McIDAS-V source code is based on IDV and VisAD source code. 016 * 017 * McIDAS-V is free software; you can redistribute it and/or modify 018 * it under the terms of the GNU Lesser Public License as published by 019 * the Free Software Foundation; either version 3 of the License, or 020 * (at your option) any later version. 021 * 022 * McIDAS-V is distributed in the hope that it will be useful, 023 * but WITHOUT ANY WARRANTY; without even the implied warranty of 024 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 025 * GNU Lesser Public License for more details. 026 * 027 * You should have received a copy of the GNU Lesser Public License 028 * along with this program. If not, see http://www.gnu.org/licenses. 029 */ 030 031 package edu.wisc.ssec.mcidasv.data.hydra; 032 033 import edu.wisc.ssec.mcidasv.data.HydraDataSource; 034 import edu.wisc.ssec.mcidasv.data.PreviewSelection; 035 036 import java.io.ByteArrayInputStream; 037 import java.io.File; 038 import java.io.FilenameFilter; 039 040 import java.rmi.RemoteException; 041 042 import java.text.SimpleDateFormat; 043 044 import java.util.ArrayList; 045 import java.util.Date; 046 import java.util.Enumeration; 047 import java.util.HashMap; 048 import java.util.Hashtable; 049 import java.util.Iterator; 050 import java.util.List; 051 import java.util.SimpleTimeZone; 052 import java.util.StringTokenizer; 053 import java.util.TimeZone; 054 import java.util.TreeSet; 055 056 import org.jdom.Namespace; 057 import org.jdom.output.XMLOutputter; 058 059 import org.slf4j.Logger; 060 import org.slf4j.LoggerFactory; 061 062 import ucar.ma2.ArrayFloat; 063 import ucar.ma2.DataType; 064 import ucar.nc2.Dimension; 065 import ucar.nc2.Group; 066 import ucar.nc2.NetcdfFile; 067 import ucar.nc2.Variable; 068 069 import ucar.unidata.data.DataCategory; 070 import ucar.unidata.data.DataChoice; 071 import ucar.unidata.data.DataSelection; 072 import ucar.unidata.data.DataSelectionComponent; 073 import ucar.unidata.data.DataSourceDescriptor; 074 import ucar.unidata.data.DirectDataChoice; 075 import ucar.unidata.data.GeoLocationInfo; 076 import ucar.unidata.data.GeoSelection; 077 078 import ucar.unidata.util.Misc; 079 080 import visad.Data; 081 import visad.FlatField; 082 import visad.VisADException; 083 084 import visad.util.Util; 085 086 /** 087 * A data source for NPOESS Preparatory Project (Suomi NPP) data 088 * This will probably move, but we are placing it here for now 089 * since we are leveraging some existing code used for HYDRA. 090 */ 091 092 public class SuomiNPPDataSource extends HydraDataSource { 093 094 private static final Logger logger = LoggerFactory.getLogger(SuomiNPPDataSource.class); 095 096 /** Sources file */ 097 protected String filename; 098 099 protected MultiDimensionReader nppAggReader; 100 101 protected MultiDimensionAdapter[] adapters = null; 102 103 private ArrayList<MultiSpectralData> multiSpectralData = new ArrayList<MultiSpectralData>(); 104 private HashMap<String, MultiSpectralData> msdMap = new HashMap<String, MultiSpectralData>(); 105 106 private static final String DATA_DESCRIPTION = "Suomi NPP Data"; 107 108 // instrument related variables and flags 109 ucar.nc2.Attribute instrumentName = null; 110 private String productName = null; 111 112 // for now, we are only handling CrIS variables that match this filter and SCAN dimensions 113 private String crisFilter = "ES_Real"; 114 115 // for now, we are only handling OMPS variables that match this filter and SCAN dimensions 116 private String ompsFilter = "Radiance"; 117 118 private HashMap defaultSubset; 119 public TrackAdapter track_adapter; 120 121 private List categories; 122 private boolean hasChannelSelect = false; 123 private boolean hasImagePreview = true; 124 private boolean isCombinedProduct = false; 125 private boolean nameHasBeenSet = false; 126 127 private FlatField previewImage = null; 128 129 private static int[] YSCAN_POSSIBILITIES = { 130 48, 96, 512, 768, 771, 771, 1536, 1541, 2304, 2313, 180, 60, 60, 60, 5, 15 131 }; 132 private static int[] XSCAN_POSSIBILITIES = { 133 254, 508, 2133, 3200, 4121, 4421, 6400, 8241, 4064, 4121, 96, 30, 30, 30, 5, 105 134 }; 135 private static int[] ZSCAN_POSSIBILITIES = { 136 -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 22, 163, 437, 717, 200, 260 137 }; 138 private int inTrackDimensionLength = -1; 139 140 // need our own separator char since it's always Unix-style in the Suomi NPP files 141 private static final String SEPARATOR_CHAR = "/"; 142 143 // date formatter for converting Suomi NPP day/time to something we can use 144 SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss.SSS"); 145 146 // date formatter for how we want to show granule day/time on display 147 SimpleDateFormat sdfOut = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z"); 148 149 /** 150 * Zero-argument constructor for construction via unpersistence. 151 */ 152 153 public SuomiNPPDataSource() {} 154 155 /** 156 * Construct a new Suomi NPP HDF5 data source. 157 * @param descriptor descriptor for this <code>DataSource</code> 158 * @param fileName name of the hdf file to read 159 * @param properties hashtable of properties 160 * 161 * @throws VisADException problem creating data 162 */ 163 164 public SuomiNPPDataSource(DataSourceDescriptor descriptor, 165 String fileName, Hashtable properties) 166 throws VisADException { 167 this(descriptor, Misc.newList(fileName), properties); 168 logger.debug("SuomiNPPDataSource called, single file selected: " + fileName); 169 } 170 171 /** 172 * Construct a new Suomi NPP HDF5 data source. 173 * @param descriptor descriptor for this <code>DataSource</code> 174 * @param sources List of filenames 175 * @param properties hashtable of properties 176 * 177 * @throws VisADException problem creating data 178 */ 179 180 public SuomiNPPDataSource(DataSourceDescriptor descriptor, 181 List<String> newSources, Hashtable properties) 182 throws VisADException { 183 super(descriptor, newSources, DATA_DESCRIPTION, properties); 184 logger.debug("SuomiNPPDataSource constructor called, file count: " + sources.size()); 185 186 filename = (String) sources.get(0); 187 setDescription("Suomi NPP"); 188 189 for (Object o : sources) { 190 logger.debug("Suomi NPP source file: " + (String) o); 191 } 192 193 setup(); 194 } 195 196 public void setup() throws VisADException { 197 198 // looking to populate 3 things - path to lat, path to lon, path to relevant products 199 String pathToLat = null; 200 String pathToLon = null; 201 TreeSet<String> pathToProducts = new TreeSet<String>(); 202 203 // flag to indicate data is 3-dimensions (X, Y, channel or band) 204 boolean is3D = false; 205 206 // check source filenames to see if this is a combined product 207 // XXX TJJ - looking for "underscore" is NOT GUARANTEED TO WORK! FIXME 208 String prodStr = filename.substring( 209 filename.lastIndexOf(File.separatorChar) + 1, 210 filename.lastIndexOf(File.separatorChar) + 1 + filename.indexOf("_")); 211 StringTokenizer st = new StringTokenizer(prodStr, "-"); 212 logger.debug("check for embedded GEO, tokenizing: " + prodStr); 213 while (st.hasMoreTokens()) { 214 String singleProd = st.nextToken(); 215 logger.debug("Next token: " + singleProd); 216 for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) { 217 if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) { 218 logger.debug("Setting isCombinedProduct true, Found embedded GEO: " + singleProd); 219 isCombinedProduct = true; 220 break; 221 } 222 } 223 } 224 225 // various metatdata we'll need to gather on a per-product basis 226 ArrayList<String> unsignedFlags = new ArrayList<String>(); 227 ArrayList<String> unpackFlags = new ArrayList<String>(); 228 229 // time for each product in milliseconds since epoch 230 ArrayList<Long> productTimes = new ArrayList<Long>(); 231 232 // geo product IDs for each granule 233 ArrayList<String> geoProductIDs = new ArrayList<String>(); 234 235 // aggregations will use sets of NetCDFFile readers 236 ArrayList<NetCDFFile> ncdfal = new ArrayList<NetCDFFile>(); 237 238 // we should be able to find an XML Product Profile for each data/product type 239 SuomiNPPProductProfile nppPP = null; 240 241 sdf.setTimeZone(TimeZone.getTimeZone("GMT")); 242 243 try { 244 245 nppPP = new SuomiNPPProductProfile(); 246 247 // for each source file provided get the nominal time 248 for (int fileCount = 0; fileCount < sources.size(); fileCount++) { 249 // need to open the main NetCDF file to determine the geolocation product 250 NetcdfFile ncfile = null; 251 String fileAbsPath = null; 252 try { 253 fileAbsPath = (String) sources.get(fileCount); 254 logger.debug("Trying to open file: " + fileAbsPath); 255 ncfile = NetcdfFile.open(fileAbsPath); 256 if (! isCombinedProduct) { 257 ucar.nc2.Attribute a = ncfile 258 .findGlobalAttribute("N_GEO_Ref"); 259 logger.debug("Value of GEO global attribute: " 260 + a.getStringValue()); 261 String tmpGeoProductID = a.getStringValue(); 262 geoProductIDs.add(tmpGeoProductID); 263 } 264 Group rg = ncfile.getRootGroup(); 265 266 logger.debug("Root group name: " + rg.getName()); 267 List<Group> gl = rg.getGroups(); 268 if (gl != null) { 269 for (Group g : gl) { 270 logger.debug("Group name: " + g.getName()); 271 // when we find the Data_Products group, go down another group level and pull out 272 // what we will use for nominal day and time (for now anyway). 273 // XXX TJJ fileCount check is so we don't count the GEO file in time array! 274 if (g.getName().contains("Data_Products") && (fileCount != sources.size())) { 275 boolean foundDateTime = false; 276 List<Group> dpg = g.getGroups(); 277 278 // cycle through once looking for XML Product Profiles 279 for (Group subG : dpg) { 280 281 // determine the instrument name (VIIRS, ATMS, CrIS, OMPS) 282 instrumentName = subG.findAttribute("Instrument_Short_Name"); 283 284 // This is also where we find the attribute which tells us which 285 // XML Product Profile to use! 286 ucar.nc2.Attribute axpp = subG.findAttribute("N_Collection_Short_Name"); 287 if (axpp != null) { 288 String baseName = axpp.getStringValue(); 289 productName = baseName; 290 String productProfileFileName = nppPP.getProfileFileName(baseName); 291 logger.debug("Found profile: " + productProfileFileName); 292 if (productProfileFileName == null) { 293 throw new Exception("XML Product Profile not found in catalog"); 294 } 295 try { 296 nppPP.addMetaDataFromFile(productProfileFileName); 297 } catch (Exception nppppe) { 298 logger.error("Error parsing XML Product Profile: " + productProfileFileName); 299 throw new Exception("XML Product Profile Error"); 300 } 301 } 302 } 303 304 // 2nd pass through sub-group to extract date/time for aggregation 305 for (Group subG : dpg) { 306 List<Variable> vl = subG.getVariables(); 307 for (Variable v : vl) { 308 ucar.nc2.Attribute aDate = v.findAttribute("AggregateBeginningDate"); 309 ucar.nc2.Attribute aTime = v.findAttribute("AggregateBeginningTime"); 310 // did we find the attributes we are looking for? 311 if ((aDate != null) && (aTime != null)) { 312 String sDate = aDate.getStringValue(); 313 String sTime = aTime.getStringValue(); 314 logger.debug("For day/time, using: " + sDate + sTime.substring(0, sTime.indexOf('Z') - 3)); 315 Date d = sdf.parse(sDate + sTime.substring(0, sTime.indexOf('Z') - 3)); 316 productTimes.add(new Long(d.getTime())); 317 logger.debug("ms since epoch: " + d.getTime()); 318 foundDateTime = true; 319 // set time for display to day/time of 1st granule examined 320 if (! nameHasBeenSet) { 321 sdfOut.setTimeZone(new SimpleTimeZone(0, "UTC")); 322 setName(instrumentName.getStringValue() + " " + sdfOut.format(d)); 323 nameHasBeenSet = true; 324 } 325 break; 326 } 327 } 328 if (foundDateTime) break; 329 } 330 if (! foundDateTime) { 331 throw new VisADException("No date time found in Suomi NPP granule"); 332 } 333 } 334 } 335 } 336 } catch (Exception e) { 337 logger.debug("Exception during processing of file: " + fileAbsPath); 338 throw (e); 339 } finally { 340 ncfile.close(); 341 } 342 } 343 344 for (Long l : productTimes) { 345 logger.debug("Product time: " + l); 346 } 347 348 // build each union aggregation element 349 for (int elementNum = 0; elementNum < sources.size(); elementNum++) { 350 String s = (String) sources.get(elementNum); 351 352 // build an XML (NCML actually) representation of the union aggregation of these two files 353 Namespace ns = Namespace.getNamespace("http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2"); 354 org.jdom.Element root = new org.jdom.Element("netcdf", ns); 355 org.jdom.Document document = new org.jdom.Document(root); 356 357 org.jdom.Element agg = new org.jdom.Element("aggregation", ns); 358 agg.setAttribute("type", "union"); 359 360 org.jdom.Element fData = new org.jdom.Element("netcdf", ns); 361 fData.setAttribute("location", s); 362 agg.addContent(fData); 363 364 if (! isCombinedProduct) { 365 org.jdom.Element fGeo = new org.jdom.Element("netcdf", ns); 366 367 String geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1); 368 String fileNameRelative = s.substring(s.lastIndexOf(File.separatorChar) + 1); 369 // check if we have the whole file name or just the prefix 370 String geoProductID = geoProductIDs.get(elementNum); 371 if (geoProductID.endsWith("h5")) { 372 geoFilename += geoProductID; 373 } else { 374 geoFilename += geoProductID; 375 geoFilename += s.substring(s.lastIndexOf(File.separatorChar) + 6); 376 } 377 // XXX TJJ - temporary check to swap for terrain corrected geo if needed. 378 // This is until we learn the formal logic for which geo to look for/use 379 File tmpGeo = new File(geoFilename); 380 if (! tmpGeo.exists()) { 381 // this one looks for GMTCO instead of GMODO 382 String geoFileRelative = geoFilename.substring(geoFilename.lastIndexOf(File.separatorChar) + 1); 383 if (fileNameRelative.startsWith("SVM")) { 384 geoFileRelative = geoFileRelative.replace("OD", "TC"); 385 } 386 if (fileNameRelative.startsWith("SVI")) { 387 geoFileRelative = geoFileRelative.replace("MG", "TC"); 388 } 389 390 // now we make a file filter, and see if a matching geo file is present 391 File fList = new File(geoFilename.substring(0, geoFilename.lastIndexOf(File.separatorChar) + 1)); // current directory 392 393 FilenameFilter geoFilter = new FilenameFilter() { 394 public boolean accept(File dir, String name) { 395 if ((name.startsWith("G")) && (name.endsWith(".h5"))) { 396 return true; 397 } else { 398 return false; 399 } 400 } 401 }; 402 403 File[] files = fList.listFiles(geoFilter); 404 for (File file : files) { 405 if (file.isDirectory()) { 406 continue; 407 } 408 // get the file name for convenience 409 String fName = file.getName(); 410 // is it one of the geo types we are looking for? 411 if (fName.substring(0, 5).equals(geoFileRelative.substring(0, 5))) { 412 int geoStartIdx = geoFileRelative.indexOf("_d"); 413 int prdStartIdx = fName.indexOf("_d"); 414 String s1 = geoFileRelative.substring(geoStartIdx, geoStartIdx + 35); 415 String s2 = fName.substring(prdStartIdx, prdStartIdx + 35); 416 if (s1.equals(s2)) { 417 geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1) + fName; 418 break; 419 } 420 } 421 } 422 } 423 logger.debug("Cobbled together GEO file name: " + geoFilename); 424 fGeo.setAttribute("location", geoFilename); 425 agg.addContent(fGeo); 426 } 427 428 root.addContent(agg); 429 XMLOutputter xmlOut = new XMLOutputter(); 430 String ncmlStr = xmlOut.outputString(document); 431 ByteArrayInputStream is = new ByteArrayInputStream(ncmlStr.getBytes()); 432 MultiDimensionReader netCDFReader = new NetCDFFile(is); 433 434 // let's try and look through the NetCDF reader and see what we can learn... 435 NetcdfFile ncdff = ((NetCDFFile) netCDFReader).getNetCDFFile(); 436 437 Group rg = ncdff.getRootGroup(); 438 439 List<Group> gl = rg.getGroups(); 440 if (gl != null) { 441 for (Group g : gl) { 442 logger.debug("Group name: " + g.getName()); 443 // XXX just temporary - we are looking through All_Data, finding displayable data 444 if (g.getName().contains("All_Data")) { 445 List<Group> adg = g.getGroups(); 446 // again, iterate through 447 for (Group subG : adg) { 448 logger.debug("Sub group name: " + subG.getName()); 449 String subName = subG.getName(); 450 if (subName.contains("-GEO")) { 451 // this is the geolocation data 452 List<Variable> vl = subG.getVariables(); 453 for (Variable v : vl) { 454 if (v.getFullName().endsWith("Latitude")) { 455 pathToLat = v.getFullName(); 456 logger.debug("Lat/Lon Variable: " + v.getFullName()); 457 } 458 if (v.getFullName().endsWith("Longitude")) { 459 pathToLon = v.getFullName(); 460 logger.debug("Lat/Lon Variable: " + v.getFullName()); 461 } 462 } 463 } else { 464 // this is the product data 465 List<Variable> vl = subG.getVariables(); 466 for (Variable v : vl) { 467 boolean useThis = false; 468 String vName = v.getFullName(); 469 logger.debug("Variable: " + vName); 470 String varShortName = vName.substring(vName.lastIndexOf(SEPARATOR_CHAR) + 1); 471 472 // skip Quality Flags for now. 473 // XXX TJJ - should we show these? if so, note they sometimes 474 // have different dimensions than the main variables. For ex, 475 // on high res bands QFs are 768 x 3200 while vars are 1536 x 6400 476 if (varShortName.startsWith("QF")) { 477 continue; 478 } 479 480 // for CrIS instrument, only taking real calibrated values for now 481 logger.debug("INSTRUMENT NAME: " + instrumentName); 482 if (instrumentName.getStringValue().equals("CrIS")) { 483 if (! varShortName.startsWith(crisFilter)) { 484 logger.debug("Skipping variable: " + varShortName); 485 continue; 486 } 487 } 488 489 // for OMPS, only Radiance for now... 490 if (instrumentName.getStringValue().contains("OMPS")) { 491 if (! varShortName.startsWith(ompsFilter)) { 492 logger.debug("Skipping variable: " + varShortName); 493 continue; 494 } 495 } 496 497 DataType dt = v.getDataType(); 498 if ((dt.getSize() != 4) && (dt.getSize() != 2) && (dt.getSize() != 1)) { 499 logger.debug("Skipping data of size: " + dt.getSize()); 500 continue; 501 } 502 List al = v.getAttributes(); 503 504 List<Dimension> dl = v.getDimensions(); 505 if (dl.size() > 4) { 506 logger.debug("Skipping data of dimension: " + dl.size()); 507 continue; 508 } 509 510 // for now, skip any 3D VIIRS data 511 if (instrumentName.getStringValue().equals("VIIRS")) { 512 if (dl.size() == 3) { 513 logger.debug("Skipping VIIRS 3D data for now..."); 514 continue; 515 } 516 } 517 518 boolean xScanOk = false; 519 boolean yScanOk = false; 520 boolean zScanOk = false; 521 for (Dimension d : dl) { 522 // in order to consider this a displayable product, make sure 523 // both scan direction dimensions are present and look like a granule 524 for (int xIdx = 0; xIdx < XSCAN_POSSIBILITIES.length; xIdx++) { 525 if (d.getLength() == XSCAN_POSSIBILITIES[xIdx]) { 526 xScanOk = true; 527 break; 528 } 529 } 530 for (int yIdx = 0; yIdx < YSCAN_POSSIBILITIES.length; yIdx++) { 531 if (d.getLength() == YSCAN_POSSIBILITIES[yIdx]) { 532 yScanOk = true; 533 inTrackDimensionLength = YSCAN_POSSIBILITIES[yIdx]; 534 break; 535 } 536 } 537 for (int zIdx = 0; zIdx < ZSCAN_POSSIBILITIES.length; zIdx++) { 538 if (d.getLength() == ZSCAN_POSSIBILITIES[zIdx]) { 539 zScanOk = true; 540 break; 541 } 542 } 543 } 544 545 if (xScanOk && yScanOk) { 546 useThis = true; 547 } 548 549 if (zScanOk) { 550 is3D = true; 551 hasChannelSelect = true; 552 logger.debug("Handling 3D data source!"); 553 } 554 555 if (useThis) { 556 // loop through the variable list again, looking for a corresponding "Factors" 557 float scaleVal = 1f; 558 float offsetVal = 0f; 559 boolean unpackFlag = false; 560 561 // if the granule has an entry for this variable name 562 // get the data, data1 = scale, data2 = offset 563 // create and poke attributes with this data 564 // endif 565 566 String factorsVarName = nppPP.getScaleFactorName(varShortName); 567 logger.debug("Mapping: " + varShortName + " to: " + factorsVarName); 568 if (factorsVarName != null) { 569 for (Variable fV : vl) { 570 if (fV.getShortName().equals(factorsVarName)) { 571 logger.debug("Pulling scale and offset values from variable: " + fV.getShortName()); 572 ucar.ma2.Array a = fV.read(); 573 float[] so = (float[]) a.copyTo1DJavaArray(); 574 //ucar.ma2.Index i = a.getIndex(); 575 //scaleVal = a.getFloat(i); 576 scaleVal = so[0]; 577 logger.debug("Scale value: " + scaleVal); 578 //i.incr(); 579 //offsetVal = a.getFloat(i); 580 offsetVal = so[1]; 581 logger.debug("Offset value: " + offsetVal); 582 unpackFlag = true; 583 break; 584 } 585 } 586 } 587 588 // poke in scale/offset attributes for now 589 590 ucar.nc2.Attribute a1 = new ucar.nc2.Attribute("scale_factor", scaleVal); 591 v.addAttribute(a1); 592 ucar.nc2.Attribute a2 = new ucar.nc2.Attribute("add_offset", offsetVal); 593 v.addAttribute(a2); 594 595 // add valid range and fill value attributes here 596 // try to fill in valid range 597 if (nppPP.hasNameAndMetaData(varShortName)) { 598 String rangeMin = nppPP.getRangeMin(varShortName); 599 String rangeMax = nppPP.getRangeMax(varShortName); 600 logger.debug("range min: " + rangeMin); 601 logger.debug("range max: " + rangeMax); 602 // only store range attribute if VALID range found 603 if ((rangeMin != null) && (rangeMax != null)) { 604 int [] shapeArr = new int [] { 2 }; 605 ArrayFloat af = new ArrayFloat(shapeArr); 606 try { 607 af.setFloat(0, Float.parseFloat(rangeMin)); 608 } catch (NumberFormatException nfe) { 609 af.setFloat(0, new Float(Integer.MIN_VALUE)); 610 } 611 try { 612 af.setFloat(1, Float.parseFloat(rangeMax)); 613 } catch (NumberFormatException nfe) { 614 af.setFloat(1, new Float(Integer.MAX_VALUE)); 615 } 616 ucar.nc2.Attribute rangeAtt = new ucar.nc2.Attribute("valid_range", af); 617 v.addAttribute(rangeAtt); 618 } 619 620 // check for and load fill values too... 621 622 // we need to check two places, first, the XML product profile 623 ArrayList<Float> fval = nppPP.getFillValues(varShortName); 624 625 // 2nd, does the variable already have one defined? 626 // if there was already a fill value associated with this variable, make 627 // sure we bring that along for the ride too... 628 ucar.nc2.Attribute aFill = v.findAttribute("_FillValue"); 629 630 // determine size of our fill value array 631 int fvArraySize = 0; 632 if (aFill != null) fvArraySize++; 633 if (! fval.isEmpty()) fvArraySize += fval.size(); 634 int [] fillShape = new int [] { fvArraySize }; 635 636 // allocate the array 637 ArrayFloat afFill = new ArrayFloat(fillShape); 638 639 // and FINALLY, fill it! 640 if (! fval.isEmpty()) { 641 for (int fillIdx = 0; fillIdx < fval.size(); fillIdx++) { 642 afFill.setFloat(fillIdx, fval.get(fillIdx)); 643 logger.debug("Adding fill value (from XML): " + fval.get(fillIdx)); 644 } 645 } 646 647 if (aFill != null) { 648 Number n = aFill.getNumericValue(); 649 // is the data unsigned? 650 ucar.nc2.Attribute aUnsigned = v.findAttribute("_Unsigned"); 651 float fillValAsFloat = Float.NaN; 652 if (aUnsigned != null) { 653 if (aUnsigned.getStringValue().equals("true")) { 654 DataType fvdt = aFill.getDataType(); 655 logger.debug("Data String: " + aFill.toString()); 656 logger.debug("DataType primitive type: " + fvdt.getPrimitiveClassType()); 657 // signed byte that needs conversion? 658 if (fvdt.getPrimitiveClassType() == byte.class) { 659 fillValAsFloat = (float) Util.unsignedByteToInt(n.byteValue()); 660 } 661 else if (fvdt.getPrimitiveClassType() == short.class) { 662 fillValAsFloat = (float) Util.unsignedShortToInt(n.shortValue()); 663 } else { 664 fillValAsFloat = n.floatValue(); 665 } 666 } 667 } 668 afFill.setFloat(fvArraySize - 1, fillValAsFloat); 669 logger.debug("Adding fill value (from variable): " + fillValAsFloat); 670 } 671 ucar.nc2.Attribute fillAtt = new ucar.nc2.Attribute("_FillValue", afFill); 672 v.addAttribute(fillAtt); 673 } 674 675 ucar.nc2.Attribute aUnsigned = v.findAttribute("_Unsigned"); 676 if (aUnsigned != null) { 677 logger.debug("_Unsigned attribute value: " + aUnsigned.getStringValue()); 678 unsignedFlags.add(aUnsigned.getStringValue()); 679 } else { 680 unsignedFlags.add("false"); 681 } 682 683 if (unpackFlag) { 684 unpackFlags.add("true"); 685 } else { 686 unpackFlags.add("false"); 687 } 688 689 logger.debug("Adding product: " + v.getShortName()); 690 pathToProducts.add(v.getFullName()); 691 692 } 693 } 694 } 695 } 696 } 697 } 698 } 699 700 ncdfal.add((NetCDFFile) netCDFReader); 701 } 702 703 } catch (Exception e) { 704 logger.error("cannot create NetCDF reader for files selected"); 705 if (e.getMessage() != null && e.getMessage().equals("XML Product Profile Error")) { 706 throw new VisADException("Unable to extract metadata from required XML Product Profile"); 707 } 708 } 709 710 // initialize the aggregation reader object 711 try { 712 nppAggReader = new GranuleAggregation(ncdfal, inTrackDimensionLength, "Track", "XTrack"); 713 } catch (Exception e) { 714 throw new VisADException("Unable to initialize aggregation reader"); 715 } 716 717 // make sure we found valid data 718 if (pathToProducts.size() == 0) { 719 throw new VisADException("No data found in files selected"); 720 } 721 722 logger.debug("Number of adapters needed: " + pathToProducts.size()); 723 adapters = new MultiDimensionAdapter[pathToProducts.size()]; 724 Hashtable<String, String[]> properties = new Hashtable<String, String[]>(); 725 726 Iterator<String> iterator = pathToProducts.iterator(); 727 int pIdx = 0; 728 while (iterator.hasNext()) { 729 String pStr = (String) iterator.next(); 730 logger.debug("Working on adapter number " + (pIdx + 1)); 731 HashMap<String, Object> swathTable = SwathAdapter.getEmptyMetadataTable(); 732 HashMap<String, Object> spectTable = SpectrumAdapter.getEmptyMetadataTable(); 733 swathTable.put("array_name", pStr); 734 swathTable.put("lon_array_name", pathToLon); 735 swathTable.put("lat_array_name", pathToLat); 736 swathTable.put("XTrack", "XTrack"); 737 swathTable.put("Track", "Track"); 738 swathTable.put("geo_Track", "Track"); 739 swathTable.put("geo_XTrack", "XTrack"); 740 swathTable.put("product_name", productName); 741 742 // array_name common to spectrum table 743 spectTable.put("array_name", pStr); 744 spectTable.put("product_name", productName); 745 logger.debug("Product Name: " + productName); 746 747 if (is3D) { 748 749 // 3D data is either ATMS, OMPS, or CrIS 750 if ((instrumentName.getName() != null) && (instrumentName.getStringValue().equals("ATMS"))) { 751 //hasChannelSelect = true; 752 spectTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 753 swathTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 754 755 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 756 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 757 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 758 spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 759 spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 760 spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 761 762 spectTable.put(SpectrumAdapter.channelType, "wavelength"); 763 spectTable.put(SpectrumAdapter.channels_name, "Channel"); 764 spectTable.put(SpectrumAdapter.x_dim_name, "XTrack"); 765 spectTable.put(SpectrumAdapter.y_dim_name, "Track"); 766 767 int numChannels = JPSSUtilities.ATMSChannelCenterFrequencies.length; 768 float[] bandArray = new float[numChannels]; 769 String[] bandNames = new String[numChannels]; 770 for (int bIdx = 0; bIdx < numChannels; bIdx++) { 771 bandArray[bIdx] = JPSSUtilities.ATMSChannelCenterFrequencies[bIdx]; 772 bandNames[bIdx] = "Channel " + (bIdx + 1); 773 } 774 spectTable.put(SpectrumAdapter.channelValues, bandArray); 775 spectTable.put(SpectrumAdapter.bandNames, bandNames); 776 777 } else { 778 if (instrumentName.getStringValue().equals("CrIS")) { 779 780 swathTable.put("XTrack", "dim1"); 781 swathTable.put("Track", "dim0"); 782 swathTable.put("geo_XTrack", "dim1"); 783 swathTable.put("geo_Track", "dim0"); 784 swathTable.put("product_name", "CrIS_SDR"); 785 swathTable.put(SpectrumAdapter.channelIndex_name, "dim3"); 786 swathTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 787 788 spectTable.put(SpectrumAdapter.channelIndex_name, "dim3"); 789 spectTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 790 spectTable.put(SpectrumAdapter.x_dim_name, "dim1"); 791 spectTable.put(SpectrumAdapter.y_dim_name, "dim0"); 792 793 } else if (instrumentName.getStringValue().contains("OMPS")) { 794 795 spectTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 796 swathTable.put(SpectrumAdapter.channelIndex_name, "Channel"); 797 798 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 799 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 800 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 801 spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"}); 802 spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 803 spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 804 805 spectTable.put(SpectrumAdapter.channelType, "wavelength"); 806 spectTable.put(SpectrumAdapter.channels_name, "Channel"); 807 spectTable.put(SpectrumAdapter.x_dim_name, "XTrack"); 808 spectTable.put(SpectrumAdapter.y_dim_name, "Track"); 809 810 int numChannels = 200; 811 if (instrumentName.getStringValue().equals("OMPS-TC")) { 812 numChannels = 260; 813 } 814 float[] bandArray = new float[numChannels]; 815 String[] bandNames = new String[numChannels]; 816 for (int bIdx = 0; bIdx < numChannels; bIdx++) { 817 bandArray[bIdx] = bIdx; 818 bandNames[bIdx] = "Channel " + (bIdx + 1); 819 } 820 spectTable.put(SpectrumAdapter.channelValues, bandArray); 821 spectTable.put(SpectrumAdapter.bandNames, bandNames); 822 823 } else { 824 // sorry, if we can't id the instrument, we can't display the data! 825 throw new VisADException("Unable to determine instrument name"); 826 } 827 } 828 829 } else { 830 swathTable.put("array_dimension_names", new String[] {"Track", "XTrack"}); 831 swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"}); 832 swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"}); 833 } 834 835 swathTable.put("scale_name", "scale_factor"); 836 swathTable.put("offset_name", "add_offset"); 837 swathTable.put("fill_value_name", "_FillValue"); 838 swathTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1)); 839 spectTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1)); 840 841 // set the valid range hash if data is available 842 if (nppPP != null) { 843 if (nppPP.getRangeMin(pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1)) != null) { 844 swathTable.put("valid_range", "valid_range"); 845 } 846 } 847 848 String unsignedAttributeStr = unsignedFlags.get(pIdx); 849 if (unsignedAttributeStr.equals("true")) { 850 swathTable.put("unsigned", unsignedAttributeStr); 851 } 852 853 String unpackFlagStr = unpackFlags.get(pIdx); 854 if (unpackFlagStr.equals("true")) { 855 swathTable.put("unpack", "true"); 856 } 857 858 // For Suomi NPP data, do valid range check AFTER applying scale/offset 859 swathTable.put("range_check_after_scaling", "true"); 860 861 // pass in a GranuleAggregation reader... 862 if (is3D) { 863 if (instrumentName.getStringValue().equals("ATMS")) { 864 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 865 SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable); 866 DataCategory.createCategory("MultiSpectral"); 867 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 868 MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 869 "BrightnessTemperature", "BrightnessTemperature", "SuomiNPP", "ATMS"); 870 msd.setInitialWavenumber(JPSSUtilities.ATMSChannelCenterFrequencies[0]); 871 multiSpectralData.add(msd); 872 } 873 if (instrumentName.getStringValue().equals("CrIS")) { 874 adapters[pIdx] = new CrIS_SDR_SwathAdapter(nppAggReader, swathTable); 875 CrIS_SDR_Spectrum csa = new CrIS_SDR_Spectrum(nppAggReader, spectTable); 876 DataCategory.createCategory("MultiSpectral"); 877 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 878 MultiSpectralData msd = new MultiSpectralData((CrIS_SDR_SwathAdapter) adapters[pIdx], 879 csa); 880 msd.setInitialWavenumber(csa.getInitialWavenumber()); 881 multiSpectralData.add(msd); 882 } 883 if (instrumentName.getStringValue().contains("OMPS")) { 884 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 885 SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable); 886 DataCategory.createCategory("MultiSpectral"); 887 categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE"); 888 MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 889 "RadianceEarth", "RadianceEarth", "SuomiNPP", "OMPS"); 890 msd.setInitialWavenumber(0); 891 multiSpectralData.add(msd); 892 } 893 if (pIdx == 0) { 894 defaultSubset = multiSpectralData.get(pIdx).getDefaultSubset(); 895 try { 896 previewImage = multiSpectralData.get(pIdx).getImage(defaultSubset); 897 } catch (Exception e) { 898 e.printStackTrace(); 899 } 900 } 901 902 } else { 903 adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable); 904 if (pIdx == 0) { 905 defaultSubset = adapters[pIdx].getDefaultSubset(); 906 } 907 categories = DataCategory.parseCategories("IMAGE"); 908 } 909 pIdx++; 910 } 911 912 setProperties(properties); 913 } 914 915 public void initAfterUnpersistence() { 916 try { 917 setup(); 918 } catch (Exception e) { 919 } 920 } 921 922 /** 923 * Make and insert the <code>DataChoice</code>-s for this 924 * <code>DataSource</code>. 925 */ 926 927 public void doMakeDataChoices() { 928 929 // special loop for CrIS, ATMS, and OMPS data 930 if (multiSpectralData.size() > 0) { 931 for (int k = 0; k < multiSpectralData.size(); k++) { 932 MultiSpectralData adapter = multiSpectralData.get(k); 933 DataChoice choice = null; 934 try { 935 choice = doMakeDataChoice(k, adapter); 936 msdMap.put(choice.getName(), adapter); 937 addDataChoice(choice); 938 } catch (Exception e) { 939 e.printStackTrace(); 940 } 941 } 942 return; 943 } 944 // all other data (VIIRS and 2D EDRs) 945 if (adapters != null) { 946 for (int idx = 0; idx < adapters.length; idx++) { 947 DataChoice choice = null; 948 try { 949 choice = doMakeDataChoice(idx, adapters[idx].getArrayName()); 950 } 951 catch (Exception e) { 952 e.printStackTrace(); 953 logger.error("doMakeDataChoice failed"); 954 } 955 956 if (choice != null) { 957 addDataChoice(choice); 958 } 959 } 960 } 961 } 962 963 private DataChoice doMakeDataChoice(int idx, String var) throws Exception { 964 String name = var; 965 DataSelection dataSel = new MultiDimensionSubset(defaultSubset); 966 Hashtable subset = new Hashtable(); 967 subset.put(new MultiDimensionSubset(), dataSel); 968 DirectDataChoice ddc = new DirectDataChoice(this, idx, name, name, categories, subset); 969 return ddc; 970 } 971 972 private DataChoice doMakeDataChoice(int idx, MultiSpectralData adapter) throws Exception { 973 String name = adapter.getName(); 974 DataSelection dataSel = new MultiDimensionSubset(defaultSubset); 975 Hashtable subset = new Hashtable(); 976 subset.put(MultiDimensionSubset.key, dataSel); 977 subset.put(MultiSpectralDataSource.paramKey, adapter.getParameter()); 978 DirectDataChoice ddc = new DirectDataChoice(this, new Integer(idx), name, name, categories, subset); 979 ddc.setProperties(subset); 980 return ddc; 981 } 982 983 /** 984 * Check to see if this <code>SuomiNPPDataSource</code> is equal to the object 985 * in question. 986 * @param o object in question 987 * @return true if they are the same or equivalent objects 988 */ 989 990 public boolean equals(Object o) { 991 if ( !(o instanceof SuomiNPPDataSource)) { 992 return false; 993 } 994 return (this == (SuomiNPPDataSource) o); 995 } 996 997 public MultiSpectralData getMultiSpectralData() { 998 return multiSpectralData.get(0); 999 } 1000 1001 public MultiSpectralData getMultiSpectralData(DataChoice choice) { 1002 return msdMap.get(choice.getName()); 1003 } 1004 1005 public String getDatasetName() { 1006 return filename; 1007 } 1008 1009 public void setDatasetName(String name) { 1010 filename = name; 1011 } 1012 1013 public HashMap getSubsetFromLonLatRect(MultiDimensionSubset select, GeoSelection geoSelection) { 1014 GeoLocationInfo ginfo = geoSelection.getBoundingBox(); 1015 return adapters[0].getSubsetFromLonLatRect(select.getSubset(), ginfo.getMinLat(), ginfo.getMaxLat(), 1016 ginfo.getMinLon(), ginfo.getMaxLon()); 1017 } 1018 1019 public synchronized Data getData(DataChoice dataChoice, DataCategory category, 1020 DataSelection dataSelection, Hashtable requestProperties) 1021 throws VisADException, RemoteException { 1022 return this.getDataInner(dataChoice, category, dataSelection, requestProperties); 1023 } 1024 1025 1026 protected Data getDataInner(DataChoice dataChoice, DataCategory category, 1027 DataSelection dataSelection, Hashtable requestProperties) 1028 throws VisADException, RemoteException { 1029 1030 //- this hack keeps the HydraImageProbe from doing a getData() 1031 //- TODO: need to use categories? 1032 if (requestProperties != null) { 1033 if ((requestProperties.toString()).equals("{prop.requester=MultiSpectral}")) { 1034 return null; 1035 } 1036 } 1037 1038 GeoLocationInfo ginfo = null; 1039 GeoSelection geoSelection = null; 1040 1041 if ((dataSelection != null) && (dataSelection.getGeoSelection() != null)) { 1042 geoSelection = (dataSelection.getGeoSelection().getBoundingBox() != null) ? dataSelection.getGeoSelection() : 1043 dataChoice.getDataSelection().getGeoSelection(); 1044 } 1045 1046 if (geoSelection != null) { 1047 ginfo = geoSelection.getBoundingBox(); 1048 } 1049 1050 Data data = null; 1051 if (adapters == null) { 1052 return data; 1053 } 1054 1055 MultiDimensionAdapter adapter = null; 1056 1057 // pick the adapter with the same index as the current data choice 1058 int aIdx = 0; 1059 List<DataChoice> dcl = getDataChoices(); 1060 for (DataChoice dc : dcl) { 1061 if (dc.equals(dataChoice)) { 1062 aIdx = dcl.indexOf(dc); 1063 break; 1064 } 1065 } 1066 1067 logger.debug("Found dataChoice index: " + aIdx); 1068 adapter = adapters[aIdx]; 1069 1070 try { 1071 HashMap subset = null; 1072 if (ginfo != null) { 1073 logger.debug("getting subset from lat-lon rect..."); 1074 subset = adapter.getSubsetFromLonLatRect(ginfo.getMinLat(), ginfo.getMaxLat(), 1075 ginfo.getMinLon(), ginfo.getMaxLon(), 1076 geoSelection.getXStride(), 1077 geoSelection.getYStride(), 1078 geoSelection.getZStride()); 1079 } 1080 else { 1081 1082 MultiDimensionSubset select = null; 1083 Hashtable table = dataChoice.getProperties(); 1084 Enumeration keys = table.keys(); 1085 while (keys.hasMoreElements()) { 1086 Object key = keys.nextElement(); 1087 logger.debug("Key: " + key.toString()); 1088 if (key instanceof MultiDimensionSubset) { 1089 select = (MultiDimensionSubset) table.get(key); 1090 } 1091 } 1092 subset = select.getSubset(); 1093 logger.debug("Subset size: " + subset.size()); 1094 1095 if (dataSelection != null) { 1096 Hashtable props = dataSelection.getProperties(); 1097 if (props != null) { 1098 if (props.containsKey(SpectrumAdapter.channelIndex_name)) { 1099 logger.debug("Props contains channel index key..."); 1100 double[] coords = (double[]) subset.get(SpectrumAdapter.channelIndex_name); 1101 int idx = ((Integer) props.get(SpectrumAdapter.channelIndex_name)).intValue(); 1102 coords[0] = (double)idx; 1103 coords[1] = (double)idx; 1104 coords[2] = (double)1; 1105 } 1106 } 1107 } 1108 } 1109 1110 if (subset != null) { 1111 data = adapter.getData(subset); 1112 data = applyProperties(data, requestProperties, subset, aIdx); 1113 } 1114 } catch (Exception e) { 1115 e.printStackTrace(); 1116 logger.error("getData exception e=" + e); 1117 } 1118 return data; 1119 } 1120 1121 protected Data applyProperties(Data data, Hashtable requestProperties, HashMap subset, int adapterIndex) 1122 throws VisADException, RemoteException { 1123 Data new_data = data; 1124 1125 if (requestProperties == null) { 1126 new_data = data; 1127 return new_data; 1128 } 1129 1130 return new_data; 1131 } 1132 1133 protected void initDataSelectionComponents( 1134 List<DataSelectionComponent> components, 1135 final DataChoice dataChoice) { 1136 1137 if (System.getProperty("os.name").equals("Mac OS X") && hasImagePreview && hasChannelSelect) { 1138 try { 1139 components.add(new ImageChannelSelection(new PreviewSelection(dataChoice, previewImage, null), new ChannelSelection(dataChoice))); 1140 } catch (Exception e) { 1141 e.printStackTrace(); 1142 } 1143 } 1144 else { 1145 if (hasImagePreview) { 1146 try { 1147 FlatField image = (FlatField) dataChoice.getData(null); 1148 components.add(new PreviewSelection(dataChoice, image, null)); 1149 } catch (Exception e) { 1150 logger.error("Can't make PreviewSelection: "+e); 1151 e.printStackTrace(); 1152 } 1153 } 1154 if (hasChannelSelect) { 1155 try { 1156 components.add(new ChannelSelection(dataChoice)); 1157 } 1158 catch (Exception e) { 1159 e.printStackTrace(); 1160 } 1161 } 1162 } 1163 1164 } 1165 1166 }