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}