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 */ 028package edu.wisc.ssec.mcidasv.chooser; 029 030import java.awt.BorderLayout; 031import java.io.File; 032import java.text.ParseException; 033import java.text.SimpleDateFormat; 034import java.util.ArrayList; 035import java.util.Arrays; 036import java.util.Collections; 037import java.util.Date; 038import java.util.Vector; 039 040import javax.swing.JFileChooser; 041import javax.swing.JOptionPane; 042import javax.swing.JPanel; 043import javax.swing.filechooser.FileFilter; 044 045import org.slf4j.Logger; 046import org.slf4j.LoggerFactory; 047 048import edu.wisc.ssec.mcidasv.data.hydra.JPSSUtilities; 049import ucar.unidata.idv.chooser.IdvChooserManager; 050import ucar.unidata.util.StringUtil; 051 052public class SuomiNPPChooser extends FileChooser { 053 054 private static final long serialVersionUID = 1L; 055 private static final Logger logger = LoggerFactory.getLogger(SuomiNPPChooser.class); 056 private static final long CONSECUTIVE_GRANULE_MAX_GAP_MS = 60000; 057 private static final long CONSECUTIVE_GRANULE_MAX_GAP_MS_NASA = 360000; 058 059 // date formatters for converting Suomi NPP day/time from file name for consecutive granule check 060 private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmSSS"); 061 private static final SimpleDateFormat sdfNASA = new SimpleDateFormat("yyyyMMddHHmm"); 062 063 /** 064 * Create the chooser with the given manager and xml 065 * 066 * @param mgr The manager 067 * @param root The xml 068 * 069 */ 070 071 public SuomiNPPChooser(IdvChooserManager mgr, org.w3c.dom.Element root) { 072 super(mgr, root); 073 } 074 075 /** 076 * Make the file chooser 077 * 078 * @param path the initial path 079 * 080 * @return the file chooser 081 */ 082 083 protected JFileChooser doMakeFileChooser(String path) { 084 if (fileChooser == null) { 085 logger.debug("Creating Suomi NPP File Chooser..."); 086 fileChooser = new SuomiNPPFileChooser(path, this); 087 } else { 088 logger.debug("2nd call to doMakeFileChooser, why?"); 089 } 090 return fileChooser; 091 } 092 093 /** 094 * Handle the selection of the set of files 095 * 096 * @param files The files the user chose 097 * @param directory The directory they chose them from 098 * @return True if the file was successful 099 * @throws Exception 100 */ 101 102 protected boolean selectFilesInner(File[] files, File directory) 103 throws Exception { 104 if ((files == null) || (files.length == 0)) { 105 userMessage("Please select a file"); 106 return false; 107 } 108 109 // make a list of just the file names 110 ArrayList<String> fileNames = new ArrayList<String>(); 111 for (int i = 0; i < files.length; i++) { 112 fileNames.add(files[i].getName()); 113 } 114 115 // ensure these files make sense as a set to create a single SNPP data source 116 if (! JPSSUtilities.isValidSet(fileNames)) { 117 JOptionPane.showMessageDialog(null, 118 "Unable to group selected data as a single data source."); 119 return false; 120 } 121 122 // ensure these files make sense as a set to create a single SNPP data source 123 if (! JPSSUtilities.hasCommonGeo(fileNames, directory)) { 124 JOptionPane.showMessageDialog(null, 125 "Unable to group selected data as a single data source."); 126 return false; 127 } 128 129 // At present, Suomi NPP chooser only allows selecting sets of consecutive granules 130 int granulesAreConsecutive = -1; 131 // Consecutive granule check - can only aggregate a contiguous set 132 if (files.length > 1) { 133 granulesAreConsecutive = testConsecutiveGranules(files); 134 } 135 136 // Need to reverse file list, so granules are increasing time order 137 if (granulesAreConsecutive == 1) { 138 Collections.reverse(Arrays.asList(files)); 139 } 140 141 if ((granulesAreConsecutive >= 0) || (files.length == 1)) { 142 return super.selectFilesInner(files, directory); 143 } else { 144 // throw up a dialog to tell user the problem 145 JOptionPane.showMessageDialog(this, 146 "When selecting multiple granules, they must be consecutive."); 147 } 148 return false; 149 } 150 151 /** 152 * Test whether a set of files are consecutive Suomi NPP granules, 153 * any sensor. NOTE: This method works when the file list contains 154 * multiple products ONLY because once we've validate one product, 155 * the time check will be a negative number when comparing the FIRST 156 * granule of product 2 with the LAST granule of product 1. A better 157 * implementation would be to pass in the filename map like the 158 * one generated in SuomiNPPDataSource constructor. 159 * 160 * @param files 161 * @return 0 if consecutive tests pass for all files 162 * -1 if tests fail 163 * 1 if tests pass but file order is backward 164 * (decreasing time order) 165 */ 166 167 private int testConsecutiveGranules(File[] files) { 168 int testResult = -1; 169 if (files == null) return testResult; 170 171 // TJJ Jan 2016 - different checks for NASA data, 6 minutes per granule 172 File f = files[0]; 173 if (f.getName().matches(JPSSUtilities.SUOMI_NPP_REGEX_NASA)) { 174 // compare start time of current granule with end time of previous 175 // difference should be very small - under a second 176 long prvTime = -1; 177 testResult = 0; 178 for (int i = 0; i < files.length; i++) { 179 if ((files[i] != null) && !files[i].isDirectory()) { 180 if (files[i].exists()) { 181 String fileName = files[i].getName(); 182 int dateIndex = fileName.lastIndexOf("_d2") + 2; 183 int timeIndex = fileName.lastIndexOf("_t") + 2; 184 String dateStr = fileName.substring(dateIndex, dateIndex + 8); 185 String timeStr = fileName.substring(timeIndex, timeIndex + 4); 186 // pull start and end time out of file name 187 Date dS = null; 188 try { 189 dS = sdfNASA.parse(dateStr + timeStr); 190 } catch (ParseException pe) { 191 logger.error("Not recognized as valid Suomi NPP file name: " + fileName); 192 testResult = -1; 193 break; 194 } 195 long curTime = dS.getTime(); 196 // only check current with previous 197 if (prvTime > 0) { 198 // make sure time diff does not exceed allowed threshold 199 // consecutive granules should be less than 1 minute apart 200 if ((curTime - prvTime) > CONSECUTIVE_GRANULE_MAX_GAP_MS_NASA) { 201 testResult = -1; 202 break; 203 } 204 // TJJ Inq #2265, #2370. Granules need to be increasing time order 205 // to properly georeference. If they are reverse order but pass 206 // all consecutive tests, we just reverse the list before returning 207 if (curTime < prvTime) { 208 testResult = 1; 209 break; 210 } 211 } 212 prvTime = curTime; 213 } 214 } 215 } 216 // consecutive granule check for NOAA data 217 } else { 218 // compare start time of current granule with end time of previous 219 // difference should be very small - under a second 220 long prvTime = -1; 221 long prvStartTime = -1; 222 testResult = 0; 223 int lastSeparator = -1; 224 int firstUnderscore = -1; 225 String prodStr = ""; 226 String prevPrd = ""; 227 for (int i = 0; i < files.length; i++) { 228 if ((files[i] != null) && !files[i].isDirectory()) { 229 if (files[i].exists()) { 230 String fileName = files[i].getName(); 231 lastSeparator = fileName.lastIndexOf(File.separatorChar); 232 firstUnderscore = fileName.indexOf("_", lastSeparator + 1); 233 prodStr = fileName.substring(lastSeparator + 1, firstUnderscore); 234 // reset check if product changes 235 if (! prodStr.equals(prevPrd)) prvTime = -1; 236 int dateIndex = fileName.lastIndexOf("_d2") + 2; 237 int timeIndexStart = fileName.lastIndexOf("_t") + 2; 238 int timeIndexEnd = fileName.lastIndexOf("_e") + 2; 239 String dateStr = fileName.substring(dateIndex, dateIndex + 8); 240 String timeStrStart = fileName.substring(timeIndexStart, timeIndexStart + 7); 241 String timeStrEnd = fileName.substring(timeIndexEnd, timeIndexEnd + 7); 242 // sanity check on file name lengths 243 int fnLen = fileName.length(); 244 if ((dateIndex > fnLen) || (timeIndexStart > fnLen) || (timeIndexEnd > fnLen)) { 245 logger.warn("unexpected file name length for: " + fileName); 246 testResult = -1; 247 break; 248 } 249 // pull start and end time out of file name 250 Date dS = null; 251 Date dE = null; 252 try { 253 dS = sdf.parse(dateStr + timeStrStart); 254 // due to nature of Suomi NPP file name encoding, we need a special 255 // check here - end time CAN roll over to next day, while day part 256 // does not change. if this happens, we tweak the date string 257 String endDateStr = dateStr; 258 String startHour = timeStrStart.substring(0, 2); 259 String endHour = timeStrEnd.substring(0, 2); 260 if ((startHour.equals("23")) && (endHour.equals("00"))) { 261 // temporarily convert date to integer, increment, convert back 262 int tmpDate = Integer.parseInt(dateStr); 263 tmpDate++; 264 endDateStr = "" + tmpDate; 265 logger.info("Granule time spanning days case handled ok..."); 266 } 267 dE = sdf.parse(endDateStr + timeStrEnd); 268 } catch (ParseException e) { 269 logger.error("Not recognized as valid Suomi NPP file name: " + fileName); 270 testResult = -1; 271 break; 272 } 273 long curTime = dS.getTime(); 274 long endTime = dE.getTime(); 275 // only check current with previous 276 if (prvTime > 0) { 277 // make sure time diff does not exceed allowed threshold 278 // consecutive granules should be less than 1 minute apart 279 if ((curTime - prvTime) > CONSECUTIVE_GRANULE_MAX_GAP_MS) { 280 testResult = -1; 281 break; 282 } 283 // TJJ Inq #2265, #2370. Granules need to be increasing time order 284 // to properly georeference. If they are reverse order but pass 285 // all consecutive tests, we just reverse the list before returning 286 if (curTime < prvStartTime) { 287 testResult = 1; 288 break; 289 } 290 } 291 prvTime = endTime; 292 prvStartTime = curTime; 293 } 294 prevPrd = prodStr; 295 } 296 } 297 } 298 return testResult; 299 } 300 301 /** 302 * Convert the given array of File objects 303 * to an array of String file names. Only 304 * include the files that actually exist. 305 * 306 * @param files Selected files 307 * @return Selected files as Strings 308 */ 309 310 protected String[] getFileNames(File[] files) { 311 if (files == null) { 312 return (String[]) null; 313 } 314 Vector<String> v = new Vector<String>(); 315 String fileNotExistsError = ""; 316 317 // NOTE: If multiple files are selected, then missing files 318 // are not in the files array. If one file is selected and 319 // it is not there, then it is in the array and file.exists() 320 // is false 321 for (int i = 0; i < files.length; i++) { 322 if ((files[i] != null) && !files[i].isDirectory()) { 323 if ( !files[i].exists()) { 324 fileNotExistsError += "File does not exist: " + files[i] + "\n"; 325 } else { 326 v.add(files[i].toString()); 327 } 328 } 329 } 330 331 if (fileNotExistsError.length() > 0) { 332 userMessage(fileNotExistsError); 333 return null; 334 } 335 336 return v.isEmpty() 337 ? null 338 : StringUtil.listToStringArray(v); 339 } 340 341 /** 342 * Get the bottom panel for the chooser 343 * @return the bottom panel 344 */ 345 346 protected JPanel getBottomPanel() { 347 // No bottom panel at present 348 return null; 349 } 350 351 /** 352 * Get the center panel for the chooser 353 * @return the center panel 354 */ 355 356 protected JPanel getCenterPanel() { 357 JPanel centerPanel = super.getCenterPanel(); 358 359 JPanel jp = new JPanel(new BorderLayout()) { 360 public void paint(java.awt.Graphics g) { 361 FileFilter ff = fileChooser.getFileFilter(); 362 if (! (ff instanceof SuomiNPPFilter)) { 363 fileChooser.setAcceptAllFileFilterUsed(false); 364 fileChooser.setFileFilter(new SuomiNPPFilter()); 365 } 366 super.paint(g); 367 } 368 }; 369 jp.add(centerPanel); 370 371 return jp; 372 } 373 374}