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