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.io.File; 032import java.io.IOException; 033import java.util.ArrayList; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.Iterator; 037import java.util.List; 038import java.util.Map; 039import java.util.Set; 040import java.util.StringTokenizer; 041 042import org.slf4j.Logger; 043import org.slf4j.LoggerFactory; 044import ucar.nc2.Attribute; 045import ucar.nc2.NetcdfFile; 046 047/** 048 * Utility class to support Joint Polar Satellite System (JPSS) functionality. 049 * Documentation referenced is from Suomi NPP Common Data Format Control Book. 050 * See: 051 * http://npp.gsfc.nasa.gov/documents.html 052 * 053 * @author tommyj 054 * 055 */ 056 057public abstract class JPSSUtilities { 058 059 private static final Logger logger = 060 LoggerFactory.getLogger(JPSSUtilities.class); 061 062 public static final String JPSS_FIELD_SEPARATOR = "_"; 063 public static final int NASA_CREATION_DATE_INDEX = 28; 064 public static final int NOAA_CREATION_DATE_INDEX = 35; 065 066 // Flags to check for SIPS data V2.0.0 and higher 067 // Augments single fill value from prior versions 068 public static final String SIPS_BOWTIE_DELETED_FLAG = "Bowtie_Deleted"; 069 public static final String SIPS_FLAG_MEANINGS_ATTRIBUTE = "flag_meanings"; 070 public static final String SIPS_FLAG_VALUES_ATTRIBUTE = "flag_values"; 071 072 // This regular expression matches a Suomi NPP Data Product as defined by the 073 // NOAA spec in CDFCB-X Volume 1, Page 21 074 public static final String SUOMI_NPP_REGEX_NOAA = 075 // Product Id, Multiple (ex: VSSTO-GATMO-VSLTO) 076 "(\\w\\w\\w\\w\\w-)*" + 077 // Product Id, Single (ex: VSSTO) 078 "\\w\\w\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 079 // Spacecraft Id (ex: npp) 080 "\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 081 // Data Start Date (ex: dYYYYMMDD) 082 "d20[0-3]\\d[0-1]\\d[0-3]\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 083 // Data Start Time (ex: tHHMMSSS) 084 "t[0-2]\\d[0-5]\\d[0-6]\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 085 // Data Stop Time (ex: eHHMMSSS) 086 "e[0-2]\\d[0-5]\\d[0-6]\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 087 // Orbit Number (ex: b00015) 088 "b\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 089 // Creation Date (ex: cYYYYMMDDHHMMSSSSSSSS) 090 "c20[0-3]\\d[0-1]\\d[0-3]\\d[0-2]\\d[0-5]\\d[0-6]\\d\\d\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 091 // Origin (ex: navo) 092 "\\w\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 093 // Domain (ex: ops) 094 "\\w\\w\\w" + 095 // HDF5 suffix 096 ".h5"; 097 098 // This regular expression matches a VIIRS Enterprise EDR 099 public static final String JPSS_REGEX_ENTERPRISE_EDR = 100 // Product Id, Single (ex: JRR-CloudPhase) 101 "(\\w|-)*" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 102 // Version (e.g v2r3) 103 "v\\dr\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 104 // Origin (ex: npp) 105 "\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 106 // Data Start Date/Time (ex: sYYYYMMDDHHMMSSS) 107 "s20[1-3]\\d[0-1]\\d[0-3]\\d\\d\\d\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 108 // Data End Date/Time (ex: eYYYYMMDDHHMMSSS) 109 "e20[1-3]\\d[0-1]\\d[0-3]\\d\\d\\d\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 110 // Creation Date/Time (ex: cYYYYMMDDHHMMSSS) 111 "c20[1-3]\\d[0-1]\\d[0-3]\\d\\d\\d\\d\\d\\d\\d\\d" + 112 // NetCDF 4 suffix 113 ".nc"; 114 115 // This regular expression matches a Suomi NPP Data Product as defined by the 116 // NASA spec in TBD (XXX TJJ - find out where is this documented?) 117 public static final String SUOMI_NPP_REGEX_NASA = 118 // Product Id, Single (ex: VL1BM) 119 // NOTE: These are the only supported NASA data at this time 120 "(VL1BD|VL1BI|VL1BM)" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 121 // Platform - always Suomi NPP 122 "snpp" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 123 // Data Start Date (ex: dYYYYMMDD) 124 "d20[0-3]\\d[0-1]\\d[0-3]\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 125 // Data Start Time (ex: tHHMM) 126 "t[0-2]\\d[0-5]\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 127 // Creation Date (ex: cYYYYMMDDHHMMSSSSSSSS) 128 "c20[0-3]\\d[0-1]\\d[0-3]\\d\\d\\d\\d\\d\\d\\d" + 129 // NetCDF 4 suffix 130 ".nc"; 131 132 // This regular expression matches a Suomi NPP geolocation file as defined by the 133 // NASA spec in TBD 134 public static final String SUOMI_GEO_REGEX_NASA = 135 // Product Id, Single (ex: VGEOM) 136 // NOTE: This MUST match the list of product ids in static array in this file! 137 "(VGEOD|VGEOI|VGEOM)" + 138 JPSSUtilities.JPSS_FIELD_SEPARATOR + 139 // Platform - always Suomi NPP 140 "snpp" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 141 // Data Start Date (ex: dYYYYMMDD) 142 "d20[0-3]\\d[0-1]\\d[0-3]\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 143 // Data Start Time (ex: tHHMM) 144 "t[0-2]\\d[0-5]\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 145 // Creation Date (ex: cYYYYMMDDHHMMSSSSSSSS) 146 "c20[0-3]\\d[0-1]\\d[0-3]\\d\\d\\d\\d\\d\\d\\d" + 147 // NetCDF 4 suffix 148 ".nc"; 149 150 public static String[] validNASAVariableNames = { 151 "M01", 152 "M02", 153 "M03", 154 "M04", 155 "M05", 156 "M06", 157 "M07", 158 "M08", 159 "M09", 160 "M10", 161 "M11", 162 "M12", 163 "M13", 164 "M14", 165 "M15", 166 "M16", 167 "I01", 168 "I02", 169 "I03", 170 "I04", 171 "I05", 172 "DNB_observations" 173 }; 174 175 public static float[] ATMSChannelCenterFrequencies = { 176 23.8f, 177 31.4f, 178 50.3f, 179 51.76f, 180 52.8f, 181 53.596f, 182 54.40f, 183 54.94f, 184 55.50f, 185 57.29032f, 186 57.29033f, 187 57.29034f, 188 57.29035f, 189 57.29036f, 190 57.29037f, 191 88.20f, 192 165.5f, 193 183.3101f, 194 183.3102f, 195 183.3103f, 196 183.3104f, 197 183.3105f 198 }; 199 200 // the list of valid geolocation product ids 201 public static String[] geoProductIDs = { 202 "GATMO", 203 "GCRSO", 204 "GAERO", 205 "GCLDO", 206 "GDNBO", 207 "GNCCO", 208 "GIGTO", 209 "GIMGO", 210 "GITCO", 211 "GMGTO", 212 "GMODO", 213 "GMTCO", 214 "GNHFO", 215 "GOTCO", 216 "GOSCO", 217 "GONPO", 218 "GONCO", 219 "GCRIO", 220 "GATRO", 221 "IVMIM", 222 "VGEOD", 223 "VGEOI", 224 "VGEOM", 225 "VMUGE" 226 }; 227 228 // This regular expression matches a Suomi NPP geolocation granule, see 229 // NOAA spec in CDFCB-X Volume 1, Page 21 230 public static final String SUOMI_GEO_REGEX_NOAA = 231 // Geo Id, Single (ex: GMODO) 232 // NOTE: This MUST match the list of product ids in static array above! 233 "(GATMO|GCRSO|GAERO|GCLDO|GDNBO|GNCCO|GIGTO|GIMGO|GITCO|" + 234 "GMGTO|GMODO|GMTCO|GNHFO|GOTCO|GOSCO|GONPO|GONCO|GCRIO|GATRO|IVMIM|VMUGE)" + 235 JPSSUtilities.JPSS_FIELD_SEPARATOR + 236 // Spacecraft Id (ex: npp) 237 "\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 238 // Data Start Date (ex: dYYYYMMDD) 239 "d20[0-3]\\d[0-1]\\d[0-3]\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 240 // Data Start Time (ex: tHHMMSSS) 241 "t[0-2]\\d[0-5]\\d[0-6]\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 242 // Data Stop Time (ex: eHHMMSSS) 243 "e[0-2]\\d[0-5]\\d[0-6]\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 244 // Orbit Number (ex: b00015) 245 "b\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 246 // Creation Date (ex: cYYYYMMDDHHMMSSSSSSSS) 247 "c20[0-3]\\d[0-1]\\d[0-3]\\d[0-2]\\d[0-5]\\d[0-6]\\d\\d\\d\\d\\d\\d\\d" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 248 // Origin (ex: navo) 249 "\\w\\w\\w\\w" + JPSSUtilities.JPSS_FIELD_SEPARATOR + 250 // Domain (ex: ops) 251 "\\w\\w\\w" + 252 // HDF5 suffix 253 ".h5"; 254 255 /** 256 * Determine if the input variable name is a valid NASA product 257 * 258 * @param varName Variable name to validate. 259 * 260 * @return {@code true} if {@code varName} passes checks. 261 */ 262 263 public static boolean isValidNASA(String varName) { 264 boolean isValid = false; 265 if (varName == null) return isValid; 266 for (String s : validNASAVariableNames) { 267 if (s.equals(varName)) { 268 isValid = true; 269 break; 270 } 271 } 272 return isValid; 273 } 274 275 /** 276 * Determine if the set if filenames constitutes contiguous SNPP granules 277 * of the same geographic coverage. 278 * 279 * @param fileList List of files to validate. 280 * 281 * @return {@code true} if {@code fileList} passes checks. 282 */ 283 284 public static boolean isValidSet(List fileList) { 285 286 // map with filename from start date through orbit will be used for comparisons 287 Map<String, List<String>> metadataMap = new HashMap<String, List<String>>(); 288 289 // Pass 1, populate the list of products selected, and empty maps 290 for (Object o : fileList) { 291 String filename = (String) o; 292 // start at last path separator to clip off absolute paths 293 int lastSeparator = filename.lastIndexOf(File.separatorChar); 294 // NOAA style products 295 if (filename.endsWith(".h5")) { 296 // products go to first underscore, see regex above for more detail 297 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 298 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 299 if (! metadataMap.containsKey(prodStr)) { 300 List<String> l = new ArrayList<String>(); 301 metadataMap.put(prodStr, l); 302 } 303 } 304 // NASA style products 305 if (filename.endsWith(".nc")) { 306 // products end at first underscore, see regex above for more detail 307 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 308 int secondUnderscore = filename.indexOf("_", firstUnderscore + 1); 309 String prodStr = filename.substring(firstUnderscore + 1, secondUnderscore); 310 if (! metadataMap.containsKey(prodStr)) { 311 List<String> l = new ArrayList<String>(); 312 metadataMap.put(prodStr, l); 313 } 314 } 315 } 316 317 // Pass 2, build up the lists of meta data strings and full filenames 318 for (Object o : fileList) { 319 String filename = (String) o; 320 // start at last path separator to clip off absolute paths 321 int lastSeparator = filename.lastIndexOf(File.separatorChar); 322 323 // NOAA style products 324 if (filename.endsWith(".h5")) { 325 // products go to first underscore, see regex above for more detail 326 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 327 // this is the key for the maps 328 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 329 // this is the value for the meta data map - start time through orbit 330 String metaStr = filename.substring(firstUnderscore + 1, firstUnderscore + 39); 331 // get the appropriate list, add the new value 332 List<String> l = (List<String>) metadataMap.get(prodStr); 333 l.add(metaStr); 334 metadataMap.put(prodStr, l); 335 } 336 337 // NASA style products 338 if (filename.endsWith(".nc")) { 339 // products end at first underscore, see regex above for more detail 340 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 341 int secondUnderscore = filename.indexOf("_", firstUnderscore + 1); 342 // this is the key for the maps 343 String prodStr = filename.substring(firstUnderscore + 1, secondUnderscore); 344 // this is the value for the meta data map - date and time 345 int dateIndex = filename.indexOf("_d"); 346 int creationIndex = filename.indexOf("_c"); 347 String metaStr = filename.substring(dateIndex + 1, creationIndex); 348 // get the appropriate list, add the new value 349 List<String> l = (List<String>) metadataMap.get(prodStr); 350 l.add(metaStr); 351 metadataMap.put(prodStr, l); 352 } 353 } 354 355 // loop over metadata map, every list much match the one for ALL other products 356 Set<String> s = metadataMap.keySet(); 357 Iterator iterator = s.iterator(); 358 List prvList = null; 359 while (iterator.hasNext()) { 360 String key = (String) iterator.next(); 361 List l = (List) metadataMap.get(key); 362 for (int i = 0; i < l.size(); i++) { 363 if (prvList != null) { 364 if (! l.equals(prvList)) return false; 365 } 366 } 367 prvList = l; 368 } 369 370 return true; 371 } 372 373 /** 374 * Replace last substring within input string which matches the input with the 375 * provided replacement string. 376 * 377 * @param string 378 * @param substring 379 * @param replacestr 380 * @return 381 */ 382 383 public static String replaceLast(String string, String substring, String replacestr) { 384 // Sanity check on input 385 if (string == null) return null; 386 if ((substring == null) || (replacestr == null)) return string; 387 388 int index = string.lastIndexOf(substring); 389 390 // substring not present 391 if (index == -1) 392 return string; 393 // it's there, swap it 394 return string.substring(0, index) + replacestr 395 + string.substring(index + substring.length()); 396 } 397 398 /** 399 * Determine if a set if filenames which constitutes contiguous SNPP 400 * granules of various products all share the same geolocation data type. 401 * 402 * @param fileList List of files to validate. 403 * @param directory Used when {@literal "GEO"} is not embedded within one 404 * of {@code fileList}. 405 * 406 * @return {@code true} if {@code fileList} passes checks. 407 */ 408 409 public static boolean hasCommonGeo(List fileList, File directory) { 410 Set<String> s = new HashSet<String>(); 411 boolean isCombinedProduct = false; 412 413 // loop through all filenames provided 414 for (Object o : fileList) { 415 isCombinedProduct = false; 416 String filename = (String) o; 417 418 // check the case where GEO is embedded in the data granules 419 int lastSeparator = filename.lastIndexOf(File.separatorChar); 420 int firstUnderscore = filename.indexOf("_", lastSeparator + 1); 421 String prodStr = filename.substring(lastSeparator + 1, firstUnderscore); 422 StringTokenizer st = new StringTokenizer(prodStr, "-"); 423 while (st.hasMoreTokens()) { 424 String singleProd = st.nextToken(); 425 for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) { 426 if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) { 427 s.add(singleProd); 428 isCombinedProduct = true; 429 break; 430 } 431 } 432 } 433 // GEO not embedded in file, need to see which GEO file is 434 // referenced in the global attribute 435 if (! isCombinedProduct) { 436 try { 437 String fileFullPath = directory.getAbsolutePath() + File.separator + filename; 438 NetcdfFile ncfile = NetcdfFile.open(fileFullPath); 439 Attribute a = ncfile.findGlobalAttribute("N_GEO_Ref"); 440 if (a != null) { 441 String geoFromAttr = a.getStringValue().substring(0, 5); 442 s.add(geoFromAttr); 443 } 444 ncfile.close(); 445 } catch (IOException ioe) { 446 logger.error("problem reading from attribute", ioe); 447 } 448 } 449 } 450 451 // if the products chosen utilize multiple GEO types, fail the selection 452 if (s.size() > 1) return false; 453 return true; 454 } 455 456}