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