001 /* 002 * This file is part of McIDAS-V 003 * 004 * Copyright 2007-2013 005 * Space Science and Engineering Center (SSEC) 006 * University of Wisconsin - Madison 007 * 1225 W. Dayton Street, Madison, WI 53706, USA 008 * https://www.ssec.wisc.edu/mcidas 009 * 010 * All Rights Reserved 011 * 012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and 013 * some McIDAS-V source code is based on IDV and VisAD source code. 014 * 015 * McIDAS-V is free software; you can redistribute it and/or modify 016 * it under the terms of the GNU Lesser Public License as published by 017 * the Free Software Foundation; either version 3 of the License, or 018 * (at your option) any later version. 019 * 020 * McIDAS-V is distributed in the hope that it will be useful, 021 * but WITHOUT ANY WARRANTY; without even the implied warranty of 022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 023 * GNU Lesser Public License for more details. 024 * 025 * You should have received a copy of the GNU Lesser Public License 026 * along with this program. If not, see http://www.gnu.org/licenses. 027 */ 028 029 package edu.wisc.ssec.mcidasv.data.hydra; 030 031 import java.util.ArrayList; 032 import java.util.HashMap; 033 import java.util.Iterator; 034 import java.util.LinkedHashSet; 035 import java.util.List; 036 037 import org.slf4j.Logger; 038 import org.slf4j.LoggerFactory; 039 040 import edu.wisc.ssec.mcidasv.data.QualityFlag; 041 042 import ucar.ma2.Array; 043 import ucar.ma2.DataType; 044 import ucar.ma2.Index; 045 import ucar.ma2.IndexIterator; 046 import ucar.ma2.Range; 047 048 import ucar.nc2.Attribute; 049 import ucar.nc2.Dimension; 050 import ucar.nc2.NetcdfFile; 051 import ucar.nc2.Structure; 052 import ucar.nc2.Variable; 053 054 /** 055 * Provides a view and operations on a set of contiguous data granules as if they 056 * were a single granule. 057 * 058 * This file needs to implement the same signatures NetCDFFile does, 059 * but for aggregations of consecutive granules. 060 * 061 * @author tommyj 062 * 063 */ 064 065 public class GranuleAggregation implements MultiDimensionReader { 066 067 private static final Logger logger = LoggerFactory.getLogger(GranuleAggregation.class); 068 069 // this structure holds the NcML readers that get passed in 070 ArrayList<NetcdfFile> nclist = new ArrayList<NetcdfFile>(); 071 072 // this holds the MultiDimensionReaders, here NetCDFFile 073 ArrayList<NetCDFFile> ncdfal = null; 074 075 // need an ArrayList for each variable hashmap structure 076 ArrayList<HashMap<String, Variable>> varMapList = new ArrayList<HashMap<String, Variable>>(); 077 ArrayList<HashMap<String, String[]>> varDimNamesList = new ArrayList<HashMap<String, String[]>>(); 078 ArrayList<HashMap<String, Class>> varDataTypeList = new ArrayList<HashMap<String, Class>>(); 079 080 // map of granule index and granule in-track length for each variable 081 HashMap<String, HashMap<Integer, Integer>> varGranInTrackLengths = new HashMap<String, HashMap<Integer, Integer>>(); 082 HashMap<String, int[]> varAggrDimLengths = new HashMap<String, int[]>(); 083 084 // this object is used to handle granules like VIIRS Imagery EDRs, where scan 085 // gaps of varying sizes and locations in the granule must be removed. If 086 // present, an initial read with these "cut" ranges will be done before subsetting 087 HashMap<Integer, ArrayList<Range>> granCutRanges = new HashMap<Integer, ArrayList<Range>>(); 088 HashMap<Integer, Integer> granCutScans = new HashMap<Integer, Integer>(); 089 090 // except quality flags - only need one hashmap per aggregation 091 // it maps the broken out variable name back to the original packed variable name 092 HashMap<String, QualityFlag> qfMap = null; 093 094 // variable can have bulk array processor set by the application 095 HashMap<String, RangeProcessor> varToRangeProcessor = new HashMap<String, RangeProcessor>(); 096 097 private int granuleCount = -1; 098 private String inTrackDimensionName = null; 099 private String inTrackGeoDimensionName = null; 100 private String crossTrackDimensionName = null; 101 private LinkedHashSet<String> products; 102 private String origName = null; 103 private boolean isEDR = false; 104 105 public GranuleAggregation(ArrayList<NetCDFFile> ncdfal, LinkedHashSet<String> products, 106 String inTrackDimensionName, String inTrackGeoDimensionName, 107 String crossTrackDimensionName, boolean isEDR) throws Exception { 108 if (ncdfal == null) throw new Exception("No data: empty Suomi NPP aggregation object"); 109 this.inTrackDimensionName = inTrackDimensionName; 110 this.crossTrackDimensionName = crossTrackDimensionName; 111 this.inTrackGeoDimensionName = inTrackGeoDimensionName; 112 this.ncdfal = ncdfal; 113 this.products = products; 114 this.isEDR = isEDR; 115 init(ncdfal); 116 } 117 118 public GranuleAggregation(ArrayList<NetCDFFile> ncdfal, LinkedHashSet<String> products, 119 String inTrackDimensionName, String inTrackGeoDimensionName, 120 String crossTrackDimensionName) throws Exception { 121 this(ncdfal, products, inTrackDimensionName, inTrackGeoDimensionName, crossTrackDimensionName, false); 122 } 123 124 public GranuleAggregation(ArrayList<NetCDFFile> ncdfal, LinkedHashSet<String> products, 125 String inTrackDimensionName, String crossTrackDimensionName) throws Exception { 126 this(ncdfal, products, inTrackDimensionName, inTrackDimensionName, crossTrackDimensionName, false); 127 } 128 129 public GranuleAggregation(ArrayList<NetCDFFile> ncdfal, LinkedHashSet<String> products, 130 String inTrackDimensionName, String crossTrackDimensionName, boolean isEDR) throws Exception { 131 this(ncdfal, products, inTrackDimensionName, inTrackDimensionName, crossTrackDimensionName, isEDR); 132 } 133 134 public Class getArrayType(String array_name) { 135 array_name = mapNameIfQualityFlag(array_name); 136 return varDataTypeList.get(0).get(array_name); 137 } 138 139 public String[] getDimensionNames(String array_name) { 140 array_name = mapNameIfQualityFlag(array_name); 141 return varDimNamesList.get(0).get(array_name); 142 } 143 144 public int[] getDimensionLengths(String array_name) { 145 array_name = mapNameIfQualityFlag(array_name); 146 logger.debug("For var " + array_name + ", sending back dim len: " + varAggrDimLengths.get(array_name)); 147 return varAggrDimLengths.get(array_name); 148 } 149 150 private String mapNameIfQualityFlag(String array_name) { 151 // only applies if name is from a packed quality flag 152 // we pull data from the "mapped" variable name, a packed byte 153 if (qfMap != null) { 154 logger.debug("mapNameIfQualityFlag, checking key: " + array_name); 155 if (qfMap.containsKey(array_name)) { 156 origName = array_name; 157 QualityFlag qf = qfMap.get(array_name); 158 String mappedName = qf.getPackedName(); 159 logger.debug("Mapped to: " + mappedName); 160 return mappedName; 161 } 162 } 163 return array_name; 164 } 165 166 /** 167 * @return the isEDR 168 */ 169 public boolean isEDR() { 170 return isEDR; 171 } 172 173 /** 174 * @param isEDR the isEDR to set 175 */ 176 public void setEDR(boolean isEDR) { 177 this.isEDR = isEDR; 178 } 179 180 public float[] getFloatArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 181 return (float[]) readArray(array_name, start, count, stride); 182 } 183 184 public int[] getIntArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 185 return (int[]) readArray(array_name, start, count, stride); 186 } 187 188 public double[] getDoubleArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 189 return (double[]) readArray(array_name, start, count, stride); 190 } 191 192 public short[] getShortArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 193 return (short[]) readArray(array_name, start, count, stride); 194 } 195 196 public byte[] getByteArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 197 return (byte[]) readArray(array_name, start, count, stride); 198 } 199 200 public Object getArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 201 return readArray(array_name, start, count, stride); 202 } 203 204 public HDFArray getGlobalAttribute(String attr_name) throws Exception { 205 throw new Exception("GranuleAggregation.getGlobalAttributes: Unimplemented"); 206 } 207 208 public HDFArray getArrayAttribute(String array_name, String attr_name) throws Exception { 209 Variable var = varMapList.get(0).get(array_name); 210 if (var == null) return null; 211 212 Attribute attr = var.findAttribute(attr_name); 213 if (attr == null) return null; 214 215 Array attrVals = attr.getValues(); 216 DataType dataType = attr.getDataType(); 217 Object array = attrVals.copyTo1DJavaArray(); 218 219 HDFArray harray = null; 220 221 if (dataType.getPrimitiveClassType() == Float.TYPE) { 222 harray = HDFArray.make((float[])array); 223 } 224 else if (dataType.getPrimitiveClassType() == Double.TYPE) { 225 harray = HDFArray.make((double[])array); 226 } 227 else if (dataType == DataType.STRING) { 228 harray = HDFArray.make((String[])array); 229 } 230 else if (dataType.getPrimitiveClassType() == Short.TYPE) { 231 harray = HDFArray.make((short[])array); 232 } 233 else if (dataType.getPrimitiveClassType() == Integer.TYPE) { 234 harray = HDFArray.make((int[])array); 235 } 236 return harray; 237 } 238 239 public void close() throws Exception { 240 // close each NetCDF file 241 for (NetcdfFile n : nclist) { 242 n.close(); 243 } 244 } 245 246 private void init(ArrayList<NetCDFFile> ncdfal) throws Exception { 247 248 logger.debug("init in..."); 249 // make a NetCDFFile object from the NcML for each granule 250 for (NetCDFFile n : ncdfal) { 251 logger.debug("loading another NetCDF file from NcML..."); 252 NetcdfFile ncfile = n.getNetCDFFile(); 253 nclist.add(ncfile); 254 } 255 256 granuleCount = nclist.size(); 257 logger.debug("Granule count: " + granuleCount); 258 259 // All files do NOT have the same structure, so need to look at each ncfile 260 // For ex, some MODIS granules have slightly different in-track and along-track 261 // lengths 262 263 NetcdfFile ncfile = null; 264 for (int ncIdx = 0; ncIdx < nclist.size(); ncIdx++) { 265 266 // good place to initialize the cut Range ArrayList for each granule 267 Integer granuleIndex = new Integer(ncIdx); 268 ArrayList<Range> al = new ArrayList<Range>(); 269 granCutRanges.put(granuleIndex, al); 270 int cutScanCount = 0; 271 272 ncfile = nclist.get(ncIdx); 273 274 Iterator<Variable> varIter = ncfile.getVariables().iterator(); 275 while (varIter.hasNext()) { 276 Variable var = varIter.next(); 277 logger.debug("Variable " + var.getShortName() + ", Rank: " + var.getRank()); 278 varAggrDimLengths.put(var.getFullName(), new int[var.getRank()]); 279 varGranInTrackLengths.put(var.getFullName(), new HashMap<Integer, Integer>()); 280 281 // Here, let's try to check the data for EDR fill lines 282 // and if found, try to handle it by simply adjusting the dimensions 283 // for this granule. Sound like a plan? We'll see... 284 285 if (isEDR) { 286 287 logger.debug("IS an EDR, need to look for fill scans..."); 288 // look through lat grid, look for missing scans 289 String varName = var.getShortName(); 290 if (varName.endsWith("Latitude")) { 291 // iterate through the scan lines, looking for fill lines 292 // NOTE: we only need to check the first column! so set 293 // up an appropriate Range to cut the read down significantly 294 int[] shape = var.getShape(); 295 ArrayList<Range> alr = new ArrayList<Range>(); 296 alr.add(new Range(0, shape[0] - 1, 1)); 297 alr.add(new Range(0, 1, 1)); 298 Array a = var.read(alr); 299 logger.debug("Lat shape: " + shape[0] + " by " + shape[1]); 300 int scanLength = shape[1]; 301 Index index = a.getIndex(); 302 float fVal = 0.0f; 303 304 int rangeOffset = 1; 305 int rangeCount = 0; 306 boolean prvScanWasCut = false; 307 boolean needClosingRange = false; 308 boolean hadCutRanges = false; 309 boolean someMissing = false; 310 311 for (int i = 0; i < shape[0]; i++) { 312 313 someMissing = false; 314 fVal = a.getFloat(index.set(i, 0)); 315 if (fVal < -90.0f) { 316 someMissing = true; 317 } 318 319 if (someMissing) { 320 hadCutRanges = true; 321 cutScanCount++; 322 logger.debug("Found a cut scan " + (i + 1) 323 + ", last val: " + fVal); 324 if ((prvScanWasCut) || (i == 0)) { 325 if (i == 0) { 326 rangeOffset = 1; 327 } else { 328 rangeOffset = i + 2; 329 } 330 } else { 331 try { 332 // We are using 2D ranges 333 logger.debug("Adding Range: " + rangeOffset 334 + ", " + i + ", 1"); 335 al.add(new Range(rangeOffset, i, 1)); 336 logger.debug("Adding Range: " + 1 + ", " 337 + (scanLength - 1) + ", 1"); 338 al.add(new Range(0, scanLength - 1, 1)); 339 } catch (Exception e) { 340 e.printStackTrace(); 341 } 342 rangeCount = 0; 343 rangeOffset = i + 1; 344 } 345 prvScanWasCut = true; 346 } else { 347 prvScanWasCut = false; 348 rangeCount += scanLength; 349 } 350 351 // check to see if closing Range needed, good data at end 352 if ((! prvScanWasCut) && (i == (scanLength - 1))) { 353 needClosingRange = true; 354 } 355 } 356 357 if (needClosingRange) { 358 // We are using 2D ranges 359 al.add(new Range(rangeOffset, rangeOffset + shape[0] 360 - 1, 1)); 361 al.add(new Range(0, scanLength - 1, 1)); 362 logger.debug("Adding closing cut Range, offs: " 363 + rangeOffset + ", len: " + rangeCount); 364 } 365 366 // if only one contiguous range, process as a normal clean granule 367 if (! hadCutRanges) { 368 al.clear(); 369 } 370 371 granCutScans.put(granuleIndex, new Integer(cutScanCount)); 372 logger.debug("Total scans cut this granule: " 373 + cutScanCount); 374 375 } 376 } else { 377 granCutScans.put(granuleIndex, new Integer(0)); 378 logger.debug("is NOT an EDR, no need to check for fill scans..."); 379 } 380 } 381 } 382 383 for (int ncIdx = 0; ncIdx < nclist.size(); ncIdx++) { 384 385 ncfile = nclist.get(ncIdx); 386 387 HashMap<String, Variable> varMap = new HashMap<String, Variable>(); 388 HashMap<String, String[]> varDimNames = new HashMap<String, String[]>(); 389 HashMap<String, Class> varDataType = new HashMap<String, Class>(); 390 391 Iterator<Variable> varIter = ncfile.getVariables().iterator(); 392 int varInTrackIndex = -1; 393 while (varIter.hasNext()) { 394 Variable var = (Variable) varIter.next(); 395 396 logger.debug("Working on variable: " + var.getFullName()); 397 398 boolean foundProduct = false; 399 for (String s : products) { 400 if (s.contains(var.getFullName())) { 401 logger.debug("Valid product: " + var.getFullName()); 402 foundProduct = true; 403 } 404 // we'll also pass Lat and Lon, needed for nav 405 if (var.getShortName().equals("Latitude")) { 406 foundProduct = true; 407 } 408 if (var.getShortName().equals("Longitude")) { 409 foundProduct = true; 410 } 411 } 412 413 if (! foundProduct) { 414 logger.debug("Skipping variable: " + var.getFullName()); 415 continue; 416 } 417 418 if (var instanceof Structure) { 419 // simply skip these, applicable only to IASI far as I know 420 continue; 421 } 422 423 int rank = var.getRank(); 424 425 // bypass any less-than-2D variables for now... 426 if (rank < 2) { 427 logger.debug("Skipping 1D variable: " + var.getFullName()); 428 continue; 429 } 430 431 String varName = var.getFullName(); 432 varMap.put(varName, var); 433 Iterator<Dimension> dimIter = var.getDimensions().iterator(); 434 String[] dimNames = new String[rank]; 435 int[] dimLengths = new int[rank]; 436 int cnt = 0; 437 boolean notDisplayable = false; 438 varInTrackIndex = getInTrackIndex(var); 439 440 while (dimIter.hasNext()) { 441 Dimension dim = dimIter.next(); 442 String s = dim.getShortName(); 443 if ((s != null) && (!s.isEmpty())) { 444 if ((! s.equals(inTrackDimensionName)) && 445 ((! s.startsWith("Band")) && (cnt == 0)) && 446 (! varName.endsWith("Latitude")) && 447 (! varName.endsWith("Longitude")) && 448 (! s.equals(crossTrackDimensionName))) { 449 notDisplayable = true; 450 break; 451 } 452 } 453 String dimName = dim.getShortName(); 454 logger.debug("GranuleAggregation init, variable: " + varName + ", dimension name: " + dimName + ", length: " + dim.getLength()); 455 if (dimName == null) dimName = "dim" + cnt; 456 dimNames[cnt] = dimName; 457 dimLengths[cnt] = dim.getLength(); 458 cnt++; 459 } 460 461 // skip to next variable if it's not displayable data 462 if (notDisplayable) continue; 463 464 // adjust in-track dimension if needed (scans were cut) 465 int cutScans = granCutScans.get(ncIdx); 466 dimLengths[varInTrackIndex] = dimLengths[varInTrackIndex] - cutScans; 467 468 // XXX TJJ - can below block go away? Think so... 469 int[] aggrDimLengths = varAggrDimLengths.get(varName); 470 for (int i = 0; i < rank; i++) { 471 if (i == varInTrackIndex) { 472 aggrDimLengths[i] += dimLengths[i]; 473 } else { 474 aggrDimLengths[i] = dimLengths[i]; 475 } 476 } 477 478 varDimNames.put(varName, dimNames); 479 varDataType.put(varName, var.getDataType().getPrimitiveClassType()); 480 481 if (varInTrackIndex < 0) { 482 logger.debug("Skipping variable with unknown dimension: " + var.getFullName()); 483 continue; 484 } 485 486 HashMap<Integer, Integer> granIdxToInTrackLen = varGranInTrackLengths.get(varName); 487 granIdxToInTrackLen.put(ncIdx, new Integer(dimLengths[varInTrackIndex])); 488 489 dimLengths[varInTrackIndex] = dimLengths[varInTrackIndex] * granuleCount; 490 varDataType.put(varName, var.getDataType().getPrimitiveClassType()); 491 } 492 493 // add the new hashmaps to our enclosing lists 494 varMapList.add(varMap); 495 varDimNamesList.add(varDimNames); 496 varDataTypeList.add(varDataType); 497 498 } 499 } 500 501 /** 502 * Based on the names of the variable dimensions, determine the in-track index 503 * @param dimNames names of dimensions - should match static strings in relevant classes 504 * @return correct index (0 or greater), or -1 if error 505 */ 506 507 private int getInTrackIndex(Variable v) { 508 509 int index = -1; 510 boolean is2D = false; 511 boolean is3D = false; 512 513 String inTrackName = null; 514 515 // typical sanity check 516 if (v == null) return index; 517 logger.debug("getInTrackIndex called for variable: " + v.getShortName()); 518 519 // lat/lon vars have different dimension names 520 if ((v.getFullName().endsWith("Latitude")) || (v.getFullName().endsWith("Longitude"))) { 521 if (v.getFullName().startsWith("All_Data")) { 522 inTrackName = inTrackDimensionName; 523 } else { 524 inTrackName = inTrackGeoDimensionName; 525 } 526 } else { 527 inTrackName = inTrackDimensionName; 528 } 529 // pull out the dimensions 530 List<Dimension> dList = v.getDimensions(); 531 532 // right now, we only handle 2D and 3D variables. 533 // TJJ XXX it does get trickier, and we will have to expand this 534 // to deal with for example CrIS data... 535 int numDimensions = dList.size(); 536 logger.debug("Number of dimensions: " + numDimensions); 537 538 // the only 4D data right now is CrIS, return 0 539 if (numDimensions == 4) return 0; 540 541 if ((numDimensions == 2) || (numDimensions == 3)) { 542 if (numDimensions == 2) is2D = true; 543 if (numDimensions == 3) is3D = true; 544 } else { 545 return index; 546 } 547 548 // if the data is 2D, we use the SwathAdapter class, 549 // if 3D, we use the SpectrumAdapter class 550 for (int i = 0; i < numDimensions; i++) { 551 if (is2D) { 552 // XXX TJJ - if empty name, in-track index is 0 553 if ((dList.get(i).getShortName() == null) || (dList.get(i).getShortName().isEmpty())) { 554 logger.warn("Empty dimension name!, assuming in-track dim is 0"); 555 return 0; 556 } 557 if (dList.get(i).getShortName().equals(inTrackName)) { 558 index = i; 559 break; 560 } 561 } 562 if (is3D) { 563 // XXX TJJ - if empty name, in-track index is 0 564 if ((dList.get(i).getShortName() == null) || (dList.get(i).getShortName().isEmpty())) { 565 logger.warn("Empty dimension name!, assuming in-track dim is 0"); 566 return 0; 567 } 568 if (dList.get(i).getShortName().equals(inTrackName)) { 569 index = i; 570 break; 571 } 572 } 573 } 574 575 // hopefully we found the right one 576 return index; 577 } 578 579 private synchronized Object readArray(String array_name, int[] start, int[] count, int[] stride) throws Exception { 580 581 array_name = mapNameIfQualityFlag(array_name); 582 // how many dimensions are we dealing with 583 int dimensionCount = start.length; 584 585 // pull out a representative variable so we can determine which index is in-track 586 Variable vTmp = varMapList.get(0).get(array_name); 587 int vInTrackIndex = getInTrackIndex(vTmp); 588 589 int loGranuleId = 0; 590 int hiGranuleId = 0; 591 592 HashMap<Integer, Integer> granIdxToInTrackLen = varGranInTrackLengths.get(array_name); 593 int numGrans = granIdxToInTrackLen.size(); 594 595 int[] vGranuleLengths = new int[numGrans]; 596 for (int k = 0; k < numGrans; k++) { 597 vGranuleLengths[k] = granIdxToInTrackLen.get(k); 598 logger.debug("readArray, gran len: " + vGranuleLengths[k] + ", scans cut: " + granCutScans.get(k)); 599 } 600 601 int strt = start[vInTrackIndex]; 602 int stp = strt + (count[vInTrackIndex] - 1) * stride[vInTrackIndex]; 603 int cnt = 0; 604 for (int k = 0; k < numGrans; k++) { 605 int granLen = granIdxToInTrackLen.get(k); 606 cnt += granLen; 607 if (strt < cnt) { 608 loGranuleId = k; 609 break; 610 } 611 } 612 613 cnt = 0; 614 for (int k = 0; k < numGrans; k++) { 615 int granLen = granIdxToInTrackLen.get(k); 616 cnt += granLen; 617 if (stp < cnt) { 618 hiGranuleId = k; 619 break; 620 } 621 } 622 logger.debug("loGranuleId: " + loGranuleId); 623 logger.debug("hiGranuleId: " + hiGranuleId); 624 625 626 // next, we break out the offsets, counts, and strides for each granule 627 int granuleSpan = hiGranuleId - loGranuleId + 1; 628 629 logger.debug("readArray req, loGran: " + loGranuleId + ", hiGran: " + 630 hiGranuleId + ", granule span: " + granuleSpan + ", dimCount: " + dimensionCount); 631 632 for (int i = 0; i < dimensionCount; i++) { 633 logger.debug("start[" + i + "]: " + start[i]); 634 logger.debug("count[" + i + "]: " + count[i]); 635 logger.debug("stride[" + i + "]: " + stride[i]); 636 } 637 638 int [][] startSet = new int [granuleSpan][dimensionCount]; 639 int [][] countSet = new int [granuleSpan][dimensionCount]; 640 int [][] strideSet = new int [granuleSpan][dimensionCount]; 641 int countSubtotal = 0; 642 643 int inTrackTotal = 0; 644 for (int i = 0; i < loGranuleId; i++) { 645 inTrackTotal += vGranuleLengths[i]; 646 } 647 648 // this part is a little tricky - set the values for each granule we need to access for this read 649 for (int i = 0; i < granuleSpan; i++) { 650 inTrackTotal += vGranuleLengths[loGranuleId+i]; 651 for (int j = 0; j < dimensionCount; j++) { 652 // for all indeces other than the in-track index, the numbers match what was passed in 653 if (j != vInTrackIndex) { 654 startSet[i][j] = start[j]; 655 countSet[i][j] = count[j] * stride[j]; 656 strideSet[i][j] = stride[j]; 657 } else { 658 // for the in-track index, it's not so easy... 659 // for first granule, start is what's passed in 660 if (i == 0) { 661 startSet[i][j] = start[j] - (inTrackTotal - vGranuleLengths[loGranuleId]); 662 } else { 663 startSet[i][j] = (inTrackTotal - start[j]) % stride[j]; 664 // TJJ Sep 2013, zero-base starts that offset into subsequent granules 665 if (startSet[i][j] > 0) { 666 startSet[i][j]--; 667 } 668 } 669 // counts may be different for start, end, and middle granules 670 if (i == 0) { 671 // is this the first and only granule? 672 if (granuleSpan == 1) { 673 countSet[i][j] = count[j] * stride[j]; 674 // or is this the first of multiple granules... 675 } else { 676 if ((inTrackTotal - start[j]) < (count[j] * stride[j])) { 677 countSet[i][j] = inTrackTotal - start[j]; 678 } else { 679 countSet[i][j] = count[j] * stride[j]; 680 } 681 countSubtotal += countSet[i][j]; 682 } 683 } else { 684 // middle granules 685 if (i < (granuleSpan - 1)) { 686 countSet[i][j] = vGranuleLengths[loGranuleId+i]; 687 countSubtotal += countSet[i][j]; 688 } else { 689 // the end granule 690 countSet[i][j] = (count[j] * stride[j]) - countSubtotal; 691 // XXX TJJ - limiting count to valid numbers here, why?? 692 // need to revisit, see why this condition manifests 693 if (countSet[i][j] > (vGranuleLengths[loGranuleId+i] - startSet[i][j])) 694 countSet[i][j] = vGranuleLengths[loGranuleId+i] - startSet[i][j]; 695 } 696 } 697 // luckily, stride never changes 698 strideSet[i][j] = stride[j]; 699 } 700 } 701 } 702 703 int totalLength = 0; 704 int rangeListCount = 0; 705 ArrayList<Array> arrayList = new ArrayList<Array>(); 706 for (int granuleIdx = 0; granuleIdx < granuleCount; granuleIdx++) { 707 if ((granuleIdx >= loGranuleId) && (granuleIdx <= hiGranuleId)) { 708 Variable var = varMapList.get(loGranuleId + (granuleIdx-loGranuleId)).get(array_name); 709 710 if (var instanceof Structure) { 711 // what to do here? 712 } else { 713 ArrayList<Range> rangeList = new ArrayList<Range>(); 714 for (int dimensionIdx = 0; dimensionIdx < dimensionCount; dimensionIdx++) { 715 logger.debug("Creating new Range: " + startSet[rangeListCount][dimensionIdx] + 716 ", " + (startSet[rangeListCount][dimensionIdx] + countSet[rangeListCount][dimensionIdx] - 1) + ", " + strideSet[rangeListCount][dimensionIdx]); 717 Range range = new Range( 718 startSet[rangeListCount][dimensionIdx], 719 startSet[rangeListCount][dimensionIdx] + countSet[rangeListCount][dimensionIdx] - 1, 720 strideSet[rangeListCount][dimensionIdx] 721 ); 722 rangeList.add(dimensionIdx, range); 723 } 724 rangeListCount++; 725 726 // If there were chunks of fill data to remove... 727 ArrayList<Range> al = granCutRanges.get(new Integer(granuleIdx)); 728 if (! al.isEmpty()) { 729 ArrayList<Variable> varChunks = new ArrayList<Variable>(); 730 for (int rangeCount = 0; rangeCount < al.size(); rangeCount+=2) { 731 ArrayList<Range> rl = new ArrayList<Range>(); 732 rl.add(al.get(rangeCount)); 733 rl.add(al.get(rangeCount + 1)); 734 varChunks.add(var.section(rl)); 735 } 736 737 int [] newShape = var.getShape(); 738 int cutScans = granCutScans.get(granuleIdx); 739 newShape[0] = newShape[0] - cutScans; 740 logger.debug("New Shape: " + newShape[0] + ", " + newShape[1]); 741 Array single = Array.factory(var.getDataType(), newShape); 742 743 // now read variable chunk data into single contiguous array 744 int idx = 0; 745 for (Variable v : varChunks) { 746 Array data = v.read(); 747 int [] tmpShape = v.getShape(); 748 for (int tIdx = 0; tIdx < tmpShape.length; tIdx++) { 749 logger.debug("Shape[" + tIdx + "]: " + tmpShape[tIdx]); 750 } 751 IndexIterator ii = data.getIndexIterator(); 752 while (ii.hasNext()) { 753 single.setFloat(idx, ii.getFloatNext()); 754 idx++; 755 } 756 } 757 758 // finally, apply subset ranges 759 Array subarray = single.section(rangeList); 760 totalLength += subarray.getSize(); 761 arrayList.add(subarray); 762 logger.debug("Size of final data array: " + subarray.getSize()); 763 764 } else { 765 Array subarray = var.read(rangeList); 766 totalLength += subarray.getSize(); 767 arrayList.add(subarray); 768 } 769 770 } 771 // put in an empty ArrayList placeholder to retain a slot for each granule 772 } else { 773 Array emptyArray = null; 774 arrayList.add(emptyArray); 775 } 776 } 777 778 // last, concatenate the individual NetCDF arrays pulled out 779 780 Class outType; 781 Class arrayType = getArrayType(array_name); 782 RangeProcessor rngProcessor = varToRangeProcessor.get(array_name); 783 if (rngProcessor == null) { 784 outType = getArrayType(array_name); 785 } 786 else { 787 outType = java.lang.Float.TYPE; 788 } 789 Object o = java.lang.reflect.Array.newInstance(outType, totalLength); 790 791 int destPos = 0; 792 int granIdx = 0; 793 794 for (Array a : arrayList) { 795 if (a != null) { 796 Object primArray = a.copyTo1DJavaArray(); 797 primArray = processArray(array_name, arrayType, granIdx, primArray, rngProcessor, start, count); 798 System.arraycopy(primArray, 0, o, destPos, (int) a.getSize()); 799 destPos += a.getSize(); 800 } 801 granIdx++; 802 } 803 804 return o; 805 } 806 807 /** 808 * @param qfMap the qfMap to set 809 */ 810 public void setQfMap(HashMap<String, QualityFlag> qfMap) { 811 this.qfMap = qfMap; 812 } 813 814 public HashMap getVarMap() { 815 return varMapList.get(0); 816 } 817 818 public ArrayList<NetCDFFile> getReaders() { 819 return this.ncdfal; 820 } 821 822 /* pass individual granule pieces just read from dataset through the RangeProcessor */ 823 private Object processArray(String array_name, Class arrayType, int granIdx, Object values, RangeProcessor rngProcessor, int[] start, int[] count) { 824 825 if (rngProcessor == null) { 826 return values; 827 } 828 else { 829 ((AggregationRangeProcessor)rngProcessor).setWhichRangeProcessor(granIdx); 830 831 boolean processAlongMultiScaleDim = false; 832 833 if (rngProcessor.hasMultiDimensionScale()) { // this data variable has an array > 1 of scale/offsets. For example, one for each band. 834 rngProcessor.setMultiScaleIndex(start[rngProcessor.getMultiScaleDimensionIndex()]); 835 if (count[rngProcessor.getMultiScaleDimensionIndex()] > 1) { // if the multiScaleDim is > 1, use processAlongMultiScaleDim below 836 processAlongMultiScaleDim = true; 837 } 838 } 839 840 Object outArray = null; 841 842 if (processAlongMultiScaleDim) { 843 844 if (arrayType == Short.TYPE) { 845 outArray = rngProcessor.processAlongMultiScaleDim((short[])values); 846 } else if (arrayType == Byte.TYPE) { 847 outArray = rngProcessor.processAlongMultiScaleDim((byte[])values); 848 } else if (arrayType == Float.TYPE) { 849 outArray = values; 850 } else if (arrayType == Double.TYPE) { 851 outArray = values; 852 } 853 854 } 855 else { 856 857 if (arrayType == Short.TYPE) { 858 outArray = rngProcessor.processRange((short[]) values, null); 859 } else if (arrayType == Byte.TYPE) { 860 // if variable is a bit-field quality flag, apply mask 861 if (qfMap.containsKey(origName)) { 862 QualityFlag qf = qfMap.get(origName); 863 outArray = rngProcessor.processRangeQualityFlag((byte[]) values, null, qf); 864 } else { 865 outArray = rngProcessor.processRange((byte[]) values, null); 866 } 867 } else if (arrayType == Float.TYPE) { 868 outArray = rngProcessor.processRange((float[]) values, null); 869 } else if (arrayType == Double.TYPE) { 870 outArray = rngProcessor.processRange((double[]) values, null); 871 } 872 873 } 874 875 return outArray; 876 } 877 } 878 879 /* Application can supply a RangeProcessor for a variable 'arrayName' */ 880 public void addRangeProcessor(String arrayName, RangeProcessor rangeProcessor) { 881 varToRangeProcessor.put(arrayName, rangeProcessor); 882 } 883 884 }