001 /* 002 * $Id: GranuleAggregation.java,v 1.23 2012/04/10 15:32:42 rink 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 java.util.ArrayList; 034 import java.util.HashMap; 035 import java.util.Iterator; 036 import java.util.List; 037 038 import org.slf4j.Logger; 039 import org.slf4j.LoggerFactory; 040 041 import ucar.ma2.Array; 042 import ucar.ma2.DataType; 043 import ucar.ma2.Range; 044 import ucar.ma2.StructureData; 045 import ucar.ma2.StructureMembers; 046 047 import ucar.nc2.Attribute; 048 import ucar.nc2.Dimension; 049 import ucar.nc2.NetcdfFile; 050 import ucar.nc2.Structure; 051 import ucar.nc2.Variable; 052 053 /** 054 * This file should be renamed - think about that Tommy. 055 * This file needs to implement the same signatures NetCDFFile does, 056 * but for aggregations of consecutive granules. 057 * 058 * @author tommyj 059 * 060 */ 061 062 public class GranuleAggregation implements MultiDimensionReader { 063 064 private static final Logger logger = LoggerFactory.getLogger(GranuleAggregation.class); 065 066 // this structure holds the NcML readers that get passed in 067 ArrayList<NetcdfFile> nclist = new ArrayList<NetcdfFile>(); 068 069 // this holds the MultiDimensionReaders, here NetCDFFile 070 ArrayList<NetCDFFile> ncdfal = null; 071 072 // need an ArrayList for each variable hashmap structure 073 ArrayList<HashMap<String, Variable>> varMapList = new ArrayList<HashMap<String, Variable>>(); 074 ArrayList<HashMap<String, String[]>> varDimNamesList = new ArrayList<HashMap<String, String[]>>(); 075 ArrayList<HashMap<String, int[]>> varDimLengthsList = new ArrayList<HashMap<String, int[]>>(); 076 ArrayList<HashMap<String, Class>> varDataTypeList = new ArrayList<HashMap<String, Class>>(); 077 ArrayList<HashMap<String, Integer>> varGranuleRatiosList = new ArrayList<HashMap<String, Integer>>(); 078 079 // variable can have bulk array processor set by the application 080 HashMap<String, RangeProcessor> varToRangeProcessor = new HashMap<String, RangeProcessor>(); 081 082 private int granuleCount = -1; 083 private int granuleLength = -1; 084 private String inTrackDimensionName = null; 085 private String crossTrackDimensionName = null; 086 087 public GranuleAggregation(ArrayList<NetCDFFile> ncdfal, int granuleLength, String inTrackDimensionName, String crossTrackDimensionName) throws Exception { 088 if (ncdfal == null) throw new Exception("No data: empty Suomi NPP aggregation object"); 089 logger.trace("granule length: " + granuleLength + " inTrack: " + inTrackDimensionName); 090 this.granuleLength = granuleLength; 091 this.inTrackDimensionName = inTrackDimensionName; 092 this.crossTrackDimensionName = crossTrackDimensionName; 093 this.ncdfal = ncdfal; 094 init(ncdfal); 095 } 096 097 public Class getArrayType(String array_name) { 098 return varDataTypeList.get(0).get(array_name); 099 } 100 101 public String[] getDimensionNames(String array_name) { 102 logger.trace("GranuleAggregation.getDimensionNames, requested: " + array_name); 103 return varDimNamesList.get(0).get(array_name); 104 } 105 106 public int[] getDimensionLengths(String array_name) { 107 logger.trace("GranuleAggregation.getDimensionLengths, requested: " + array_name); 108 int[] lengths = varDimLengthsList.get(0).get(array_name); 109 for (int i = 0; i < lengths.length; i++) { 110 logger.trace("Length: " + lengths[i]); 111 } 112 return varDimLengthsList.get(0).get(array_name); 113 } 114 115 public float[] getFloatArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 116 return (float[]) readArray(array_name, start, count, stride); 117 } 118 119 public int[] getIntArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 120 return (int[]) readArray(array_name, start, count, stride); 121 } 122 123 public double[] getDoubleArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 124 return (double[]) readArray(array_name, start, count, stride); 125 } 126 127 public short[] getShortArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 128 return (short[]) readArray(array_name, start, count, stride); 129 } 130 131 public byte[] getByteArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 132 return (byte[]) readArray(array_name, start, count, stride); 133 } 134 135 public Object getArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 136 return readArray(array_name, start, count, stride); 137 } 138 139 public HDFArray getGlobalAttribute(String attr_name) throws Exception { 140 throw new Exception("GranuleAggregation.getGlobalAttributes: Unimplemented"); 141 } 142 143 public HDFArray getArrayAttribute(String array_name, String attr_name) throws Exception { 144 Variable var = varMapList.get(0).get(array_name); 145 if (var == null) return null; 146 147 Attribute attr = var.findAttribute(attr_name); 148 if (attr == null) return null; 149 150 Array attrVals = attr.getValues(); 151 DataType dataType = attr.getDataType(); 152 Object array = attrVals.copyTo1DJavaArray(); 153 154 HDFArray harray = null; 155 156 if (dataType.getPrimitiveClassType() == Float.TYPE) { 157 harray = HDFArray.make((float[])array); 158 } 159 else if (dataType.getPrimitiveClassType() == Double.TYPE) { 160 harray = HDFArray.make((double[])array); 161 } 162 else if (dataType == DataType.STRING) { 163 harray = HDFArray.make((String[])array); 164 } 165 else if (dataType.getPrimitiveClassType() == Short.TYPE) { 166 harray = HDFArray.make((short[])array); 167 } 168 else if (dataType.getPrimitiveClassType() == Integer.TYPE) { 169 harray = HDFArray.make((int[])array); 170 } 171 return harray; 172 } 173 174 public void close() throws Exception { 175 // close each NetCDF file 176 for (NetcdfFile n : nclist) { 177 n.close(); 178 } 179 } 180 181 private void init(ArrayList<NetCDFFile> ncdfal) throws Exception { 182 183 logger.debug("init in..."); 184 // make a NetCDFFile object from the NcML for each granule 185 for (NetCDFFile n : ncdfal) { 186 logger.debug("loading another NetCDF file from NcML..."); 187 NetcdfFile ncfile = n.getNetCDFFile(); 188 nclist.add(ncfile); 189 } 190 191 granuleCount = nclist.size(); 192 logger.debug("Granule count: " + granuleCount); 193 194 for (int ncIdx = 0; ncIdx < nclist.size(); ncIdx++) { 195 196 NetcdfFile ncfile = nclist.get(ncIdx); 197 198 HashMap<String, Variable> varMap = new HashMap<String, Variable>(); 199 HashMap<String, String[]> varDimNames = new HashMap<String, String[]>(); 200 HashMap<String, int[]> varDimLengths = new HashMap<String, int[]>(); 201 HashMap<String, Class> varDataType = new HashMap<String, Class>(); 202 HashMap<String, Integer> varGranuleRatios = new HashMap<String, Integer>(); 203 204 Iterator varIter = ncfile.getVariables().iterator(); 205 int varInTrackIndex = -1; 206 while (varIter.hasNext()) { 207 Variable var = (Variable) varIter.next(); 208 209 logger.debug("Working on variable: " + var.getShortName()); 210 211 if (var instanceof Structure) { 212 analyzeStructure((Structure) var, varMap, varDimNames, varDimLengths, varDataType); 213 continue; 214 } 215 216 int rank = var.getRank(); 217 218 // bypass any less-than-2D variables for now... 219 if (rank < 2) { 220 logger.debug("Skipping 1D variable: " + var.getShortName()); 221 continue; 222 } 223 224 String varName = var.getFullName(); 225 varMap.put(varName, var); 226 Iterator dimIter = var.getDimensions().iterator(); 227 String[] dimNames = new String[rank]; 228 int[] dimLengths = new int[rank]; 229 int cnt = 0; 230 boolean notDisplayable = false; 231 while (dimIter.hasNext()) { 232 Dimension dim = (Dimension) dimIter.next(); 233 String s = dim.getName(); 234 logger.trace("DIMENSION name: " + s); 235 if ((s != null) && (!s.isEmpty())) { 236 if ((! s.equals(inTrackDimensionName)) && 237 ((! s.startsWith("Band")) && (cnt == 0)) && 238 (! varName.endsWith("Latitude")) && 239 (! varName.endsWith("Longitude")) && 240 (! s.equals(crossTrackDimensionName))) { 241 notDisplayable = true; 242 break; 243 } 244 } 245 String dimName = dim.getName(); 246 logger.trace("GranuleAggregation init, variable: " + varName + ", dimension name: " + dimName + ", length: " + dim.getLength()); 247 if (dimName == null) dimName = "dim" + cnt; 248 dimNames[cnt] = dimName; 249 dimLengths[cnt] = dim.getLength(); 250 cnt++; 251 } 252 253 // skip to next variable if it's not displayable data 254 if (notDisplayable) continue; 255 256 varInTrackIndex = getInTrackIndex(var); 257 logger.debug("Found in-track index of: " + varInTrackIndex); 258 259 if (varInTrackIndex < 0) { 260 logger.debug("Skipping variable with unknown dimension: " + var.getShortName()); 261 continue; 262 } 263 264 // store the ratio of this variable's in-track length to the granule length 265 varGranuleRatios.put(varName, new Integer(granuleLength / dimLengths[varInTrackIndex])); 266 267 dimLengths[varInTrackIndex] = dimLengths[varInTrackIndex] * granuleCount; 268 269 varDimNames.put(varName, dimNames); 270 varDimLengths.put(varName, dimLengths); 271 varDataType.put(varName, var.getDataType().getPrimitiveClassType()); 272 } 273 274 // add the new hashmaps to our enclosing lists 275 varMapList.add(varMap); 276 varDimNamesList.add(varDimNames); 277 varDimLengthsList.add(varDimLengths); 278 varDataTypeList.add(varDataType); 279 varGranuleRatiosList.add(varGranuleRatios); 280 } 281 } 282 283 /** 284 * Based on the names of the variable dimensions, determine the in-track index 285 * @param dimNames names of dimensions - should match static strings in relevant classes 286 * @return correct index (0 or greater), or -1 if error 287 */ 288 289 private int getInTrackIndex(Variable v) { 290 291 int index = -1; 292 boolean is2D = false; 293 boolean is3D = false; 294 295 String inTrackName = null; 296 297 // typical sanity check 298 if (v == null) return index; 299 logger.debug("getInTrackIndex called for variable: " + v.getShortName()); 300 301 // lat/lon vars have different dimension names 302 if ((v.getFullName().endsWith("Latitude")) || (v.getFullName().endsWith("Longitude"))) { 303 if (v.getFullName().startsWith("All_Data")) { 304 inTrackName = inTrackDimensionName; 305 } else { 306 inTrackName = "2*nscans"; 307 } 308 } else { 309 inTrackName = inTrackDimensionName; 310 } 311 // pull out the dimensions 312 List<Dimension> dList = v.getDimensions(); 313 314 // right now, we only handle 2D and 3D variables. 315 // TJJ XXX it does get trickier, and we will have to expand this 316 // to deal with for example CrIS data... 317 int numDimensions = dList.size(); 318 logger.debug("Number of dimensions: " + numDimensions); 319 320 // the only 4D data right now is CrIS, return 0 321 if (numDimensions == 4) return 0; 322 323 if ((numDimensions == 2) || (numDimensions == 3)) { 324 if (numDimensions == 2) is2D = true; 325 if (numDimensions == 3) is3D = true; 326 } else { 327 return index; 328 } 329 330 // if the data is 2D, we use the SwathAdapter class, 331 // if 3D, we use the SpectrumAdapter class 332 for (int i = 0; i < numDimensions; i++) { 333 if (is2D) { 334 // XXX TJJ - if empty name, in-track index is 0 335 if ((dList.get(i).getName() == null) || (dList.get(i).getName().isEmpty())) { 336 logger.debug("WARNING: Empty dimension name!, assuming in-track dim is 0"); 337 return 0; 338 } 339 logger.debug("Comparing: " + dList.get(i).getName() + " with: " + inTrackName); 340 if (dList.get(i).getName().equals(inTrackName)) { 341 index = i; 342 break; 343 } 344 } 345 if (is3D) { 346 // XXX TJJ - if empty name, in-track index is 0 347 if ((dList.get(i).getName() == null) || (dList.get(i).getName().isEmpty())) { 348 logger.debug("WARNING: Empty dimension name!, assuming in-track dim is 0"); 349 return 0; 350 } 351 logger.debug("Comparing: " + dList.get(i).getName() + " with: " + inTrackName); 352 if (dList.get(i).getName().equals(inTrackName)) { 353 index = i; 354 break; 355 } 356 } 357 } 358 359 // hopefully we found the right one 360 return index; 361 } 362 363 void analyzeStructure(Structure var, HashMap<String, Variable> varMap, 364 HashMap<String, String[]> varDimNames, 365 HashMap<String, int[]> varDimLengths, 366 HashMap<String, Class> varDataType) throws Exception { 367 if ((var.getShape()).length == 0) { 368 return; 369 } 370 String varName = var.getFullName(); 371 String[] dimNames = new String[2]; 372 int[] dimLengths = new int[2]; 373 int cnt = 0; 374 dimLengths[0] = (var.getShape())[0]; 375 dimNames[0] = "dim"+cnt; 376 377 cnt++; 378 StructureData sData = var.readStructure(0); 379 List memList = sData.getMembers(); 380 dimLengths[1] = memList.size(); 381 dimNames[1] = "dim"+cnt; 382 383 varDimNames.put(varName, dimNames); 384 varDimLengths.put(varName, dimLengths); 385 varMap.put(varName, var); 386 387 StructureMembers sMembers = sData.getStructureMembers(); 388 Object obj = sData.getScalarObject(sMembers.getMember(0)); 389 varDataType.put(varName, obj.getClass()); 390 } 391 392 private synchronized Object readArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 393 394 // how many dimensions are we dealing with 395 int dimensionCount = start.length; 396 397 // pull out a representative variable so we can determine which index is in-track 398 Variable vTmp = varMapList.get(0).get(array_name); 399 int vInTrackIndex = getInTrackIndex(vTmp); 400 401 int vGranuleRatio = varGranuleRatiosList.get(0).get(array_name); 402 int vGranuleLength = granuleLength / vGranuleRatio; 403 404 logger.trace("READING: " + array_name + ", INTRACKINDEX: " + vInTrackIndex + 405 ", RATIO: " + vGranuleRatio); 406 407 // which granules will we be dealing with? 408 int loGranuleId = start[vInTrackIndex] / vGranuleLength; 409 int hiGranuleId = (start[vInTrackIndex] + ((count[vInTrackIndex] - 1) * stride[vInTrackIndex])) / vGranuleLength; 410 411 // next, we break out the offsets, counts, and strides for each granule 412 int granuleSpan = hiGranuleId - loGranuleId + 1; 413 logger.trace("readArray req, loGran: " + loGranuleId + ", hiGran: " + 414 hiGranuleId + ", granule span: " + granuleSpan + ", dimCount: " + dimensionCount); 415 for (int i = 0; i < dimensionCount; i++) { 416 logger.trace("start[" + i + "]: " + start[i]); 417 logger.trace("count[" + i + "]: " + count[i]); 418 logger.trace("stride[" + i + "]: " + stride[i]); 419 } 420 int [][] startSet = new int [granuleSpan][dimensionCount]; 421 int [][] countSet = new int [granuleSpan][dimensionCount]; 422 int [][] strideSet = new int [granuleSpan][dimensionCount]; 423 int countSubtotal = 0; 424 425 // this part is a little tricky - set the values for each granule we need to access for this read 426 int granuleNumber = loGranuleId; 427 for (int i = 0; i < granuleSpan; i++) { 428 granuleNumber++; 429 for (int j = 0; j < dimensionCount; j++) { 430 // for all indeces other than the in-track index, the numbers match what was passed in 431 if (j != vInTrackIndex) { 432 startSet[i][j] = start[j]; 433 countSet[i][j] = count[j] * stride[j]; 434 strideSet[i][j] = stride[j]; 435 } else { 436 // for the in-track index, it's not so easy... 437 // for first granule, start is what's passed in 438 if (i == 0) { 439 startSet[i][j] = start[j] % vGranuleLength; 440 } else { 441 startSet[i][j] = ((vGranuleLength * granuleNumber) - start[j]) % stride[j]; 442 } 443 // counts may be different for start, end, and middle granules 444 if (i == 0) { 445 // is this the first and only granule? 446 if (granuleSpan == 1) { 447 countSet[i][j] = count[j] * stride[j]; 448 // or is this the first of multiple granules... 449 } else { 450 if (((vGranuleLength * granuleNumber) - start[j]) < (count[j] * stride[j])) { 451 countSet[i][j] = ((vGranuleLength * granuleNumber) - start[j]); 452 } else { 453 countSet[i][j] = count[j] * stride[j]; 454 } 455 countSubtotal += countSet[i][j]; 456 } 457 } else { 458 // middle granules 459 if (i < (granuleSpan - 1)) { 460 countSet[i][j] = vGranuleLength; 461 countSubtotal += countSet[i][j]; 462 } else { 463 // the end granule 464 countSet[i][j] = (count[j] * stride[j]) - countSubtotal; 465 // XXX TJJ - limiting count to valid numbers here, why?? 466 // need to revisit, see why this condition manifests 467 if (countSet[i][j] > (vGranuleLength - startSet[i][j])) 468 countSet[i][j] = vGranuleLength - startSet[i][j]; 469 } 470 } 471 // luckily, stride never changes 472 strideSet[i][j] = stride[j]; 473 } 474 } 475 } 476 477 int totalLength = 0; 478 int rangeListCount = 0; 479 ArrayList<Array> arrayList = new ArrayList<Array>(); 480 for (int granuleIdx = 0; granuleIdx < granuleCount; granuleIdx++) { 481 if ((granuleIdx >= loGranuleId) && (granuleIdx <= hiGranuleId)) { 482 Variable var = varMapList.get(loGranuleId + (granuleIdx-loGranuleId)).get(array_name); 483 484 if (var instanceof Structure) { 485 // what to do here? 486 } else { 487 ArrayList<Range> rangeList = new ArrayList<Range>(); 488 for (int dimensionIdx = 0; dimensionIdx < dimensionCount; dimensionIdx++) { 489 logger.trace("Creating new Range: " + startSet[rangeListCount][dimensionIdx] + 490 ", " + (startSet[rangeListCount][dimensionIdx] + countSet[rangeListCount][dimensionIdx] - 1) + ", " + strideSet[rangeListCount][dimensionIdx]); 491 Range range = new Range( 492 startSet[rangeListCount][dimensionIdx], 493 startSet[rangeListCount][dimensionIdx] + countSet[rangeListCount][dimensionIdx] - 1, 494 strideSet[rangeListCount][dimensionIdx] 495 ); 496 rangeList.add(dimensionIdx, range); 497 } 498 rangeListCount++; 499 Array subarray = var.read(rangeList); 500 //dataType = subarray.getElementType(); 501 totalLength += subarray.getSize(); 502 arrayList.add(subarray); 503 } 504 // put in an empty ArrayList placeholder to retain a slot for each granule 505 } else { 506 Array emptyArray = null; 507 arrayList.add(emptyArray); 508 } 509 } 510 511 // last, concatenate the individual NetCDF arrays pulled out 512 513 Class outType; 514 Class arrayType = getArrayType(array_name); 515 RangeProcessor rngProcessor = varToRangeProcessor.get(array_name); 516 if (rngProcessor == null) { 517 outType = getArrayType(array_name); 518 } 519 else { 520 outType = java.lang.Float.TYPE; 521 } 522 Object o = java.lang.reflect.Array.newInstance(outType, totalLength); 523 524 int destPos = 0; 525 int granIdx = 0; 526 527 for (Array a : arrayList) { 528 if (a != null) { 529 Object primArray = a.copyTo1DJavaArray(); 530 primArray = processArray(array_name, arrayType, granIdx, primArray, rngProcessor, start, count); 531 System.arraycopy(primArray, 0, o, destPos, (int) a.getSize()); 532 destPos += a.getSize(); 533 } 534 granIdx++; 535 } 536 537 538 return o; 539 } 540 541 public HashMap getVarMap() { 542 return varMapList.get(0); 543 } 544 545 public ArrayList<NetCDFFile> getReaders() { 546 return this.ncdfal; 547 } 548 549 /* pass individual granule pieces just read from dataset through the RangeProcessor */ 550 private Object processArray(String array_name, Class arrayType, int granIdx, Object values, RangeProcessor rngProcessor, int[] start, int[] count) { 551 if (rngProcessor == null) { 552 return values; 553 } 554 else { 555 ((AggregationRangeProcessor)rngProcessor).setWhichRangeProcessor(granIdx); 556 557 boolean processAlongMultiScaleDim = false; 558 559 if (rngProcessor.hasMultiDimensionScale()) { // this data variable has an array > 1 of scale/offsets. For example, one for each band. 560 rngProcessor.setMultiScaleIndex(start[rngProcessor.getMultiScaleDimensionIndex()]); 561 if (count[rngProcessor.getMultiScaleDimensionIndex()] > 1) { // if the multiScaleDim is > 1, use processAlongMultiScaleDim below 562 processAlongMultiScaleDim = true; 563 } 564 } 565 566 Object outArray = null; 567 568 if (processAlongMultiScaleDim) { 569 570 if (arrayType == Short.TYPE) { 571 outArray = rngProcessor.processAlongMultiScaleDim((short[])values); 572 } else if (arrayType == Byte.TYPE) { 573 outArray = rngProcessor.processAlongMultiScaleDim((byte[])values); 574 } else if (arrayType == Float.TYPE) { 575 outArray = values; 576 } else if (arrayType == Double.TYPE) { 577 outArray = values; 578 } 579 580 } 581 else { 582 583 if (arrayType == Short.TYPE) { 584 outArray = rngProcessor.processRange((short[])values, null); 585 } else if (arrayType == Byte.TYPE) { 586 outArray = rngProcessor.processRange((byte[])values, null); 587 } else if (arrayType == Float.TYPE) { 588 outArray = rngProcessor.processRange((float[])values, null); 589 } else if (arrayType == Double.TYPE) { 590 outArray = rngProcessor.processRange((double[])values, null); 591 } 592 593 } 594 595 return outArray; 596 } 597 } 598 599 /* Application can supply a RangeProcessor for an variable 'arrayName' */ 600 public void addRangeProcessor(String arrayName, RangeProcessor rangeProcessor) { 601 varToRangeProcessor.put(arrayName, rangeProcessor); 602 } 603 }