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 edu.wisc.ssec.mcidasv.Constants;
032import edu.wisc.ssec.mcidasv.McIDASV;
033import edu.wisc.ssec.mcidasv.PersistenceManager;
034import edu.wisc.ssec.mcidasv.data.HydraDataSource;
035import edu.wisc.ssec.mcidasv.data.PreviewSelection;
036import edu.wisc.ssec.mcidasv.data.QualityFlag;
037
038import java.io.ByteArrayInputStream;
039import java.io.File;
040import java.io.FilenameFilter;
041import java.rmi.RemoteException;
042import java.text.SimpleDateFormat;
043import java.util.ArrayList;
044import java.util.Date;
045import java.util.Enumeration;
046import java.util.HashMap;
047import java.util.Hashtable;
048import java.util.Iterator;
049import java.util.LinkedHashMap;
050import java.util.LinkedHashSet;
051import java.util.List;
052import java.util.Map;
053import java.util.Set;
054import java.util.SimpleTimeZone;
055import java.util.StringTokenizer;
056
057import javax.swing.JCheckBox;
058import javax.swing.JOptionPane;
059
060import org.jdom2.Document;
061import org.jdom2.Element;
062import org.jdom2.Namespace;
063import org.jdom2.output.XMLOutputter;
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067import ucar.ma2.ArrayFloat;
068import ucar.ma2.DataType;
069import ucar.nc2.Attribute;
070import ucar.nc2.Dimension;
071import ucar.nc2.Group;
072import ucar.nc2.NetcdfFile;
073import ucar.nc2.Variable;
074import ucar.nc2.dataset.VariableDS;
075import ucar.unidata.data.DataCategory;
076import ucar.unidata.data.DataChoice;
077import ucar.unidata.data.DataSelection;
078import ucar.unidata.data.DataSelectionComponent;
079import ucar.unidata.data.DataSourceDescriptor;
080import ucar.unidata.data.DirectDataChoice;
081import ucar.unidata.data.GeoLocationInfo;
082import ucar.unidata.data.GeoSelection;
083import ucar.unidata.data.grid.GridUtil;
084import ucar.unidata.idv.IdvPersistenceManager;
085import ucar.unidata.util.Misc;
086import visad.Data;
087import visad.DateTime;
088import visad.DerivedUnit;
089import visad.FieldImpl;
090import visad.FlatField;
091import visad.FunctionType;
092import visad.RealType;
093import visad.SampledSet;
094import visad.Unit;
095import visad.VisADException;
096import visad.data.units.NoSuchUnitException;
097import visad.data.units.ParseException;
098import visad.data.units.Parser;
099import visad.util.Util;
100
101/**
102 * A data source for NPOESS Preparatory Project (Suomi NPP) data
103 * This will probably move, but we are placing it here for now
104 * since we are leveraging some existing code used for HYDRA.
105 */
106
107public class SuomiNPPDataSource extends HydraDataSource {
108
109        private static final Logger logger = LoggerFactory.getLogger(SuomiNPPDataSource.class);
110        
111        /** Sources file */
112    protected String filename;
113    
114    // for loading bundles, store granule lists and geo lists here
115    protected List<String> oldSources = new ArrayList<>();
116    protected List<String> geoSources = new ArrayList<>();
117    
118    // integrity map for grouping sets/aggregations of selected products
119    Map<String, List<String>> filenameMap = null;
120
121    protected MultiDimensionReader nppAggReader;
122
123    protected MultiDimensionAdapter[] adapters = null;
124    
125    private List<MultiSpectralData> msd_CrIS = new ArrayList<>();
126    private List<MultiSpectralData> multiSpectralData = new ArrayList<>();
127    private Map<String, MultiSpectralData> msdMap = new HashMap<>();
128    private Map<String, QualityFlag> qfMap = new HashMap<>();
129    private Map<String, float[]> lutMap = new HashMap<>();
130
131    private static final String DATA_DESCRIPTION = "Suomi NPP Data";
132    
133    // instrument related variables and flags
134    Attribute instrumentName = null;
135    private String productName = null;
136    
137    // product related variables and flags
138    String whichEDR = "";
139    
140    // for now, we are only handling CrIS variables that match this filter and SCAN dimensions
141    private String crisFilter = "ES_Real";
142    
143    // for now, we are only handling OMPS variables that match this filter and SCAN dimensions
144    private String ompsFilter = "Radiance";
145
146    private Map<String, double[]> defaultSubset;
147    public TrackAdapter track_adapter;
148
149    private List categories;
150    private boolean isCombinedProduct = false;
151    private boolean nameHasBeenSet = false;
152    
153    // need our own separator char since it's always Unix-style in the Suomi NPP files
154    private static final String SEPARATOR_CHAR = "/";
155    
156    // date formatter for NASA L1B data, ex 2016-02-07T00:06:00.000Z
157    SimpleDateFormat sdfNASA = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");
158    
159    // LUTs for NASA L1B data
160    float[] m12LUT = null;
161    float[] m13LUT = null;
162    float[] m14LUT = null;
163    float[] m15LUT = null;
164    float[] m16LUT = null;
165    float[] i04LUT = null;
166    float[] i05LUT = null;
167    
168    // Map to match NASA variables to units (XML Product Profiles used for NOAA)
169    Map<String, String> unitsNASA = new HashMap<String, String>();
170    
171    // date formatter for converting Suomi NPP day/time to something we can use
172    SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
173    
174    // date formatter for how we want to show granule day/time on display
175    SimpleDateFormat sdfOut = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
176    
177    // MJH keep track of date to add time dim to FieldImpl
178    Date theDate;
179
180    /**
181     * Zero-argument constructor for construction via unpersistence.
182     */
183    
184    public SuomiNPPDataSource() {
185    }
186    
187    public SuomiNPPDataSource(String fileName) throws VisADException {
188        this(null, Misc.newList(fileName), null);
189        logger.debug("filename only constructor call..");
190    }
191
192    /**
193     * Construct a new Suomi NPP HDF5 data source.
194     * @param  descriptor  descriptor for this {@code DataSource}
195     * @param  fileName  name of the hdf file to read
196     * @param  properties  hashtable of properties
197     *
198     * @throws VisADException problem creating data
199     */
200    
201    public SuomiNPPDataSource(DataSourceDescriptor descriptor,
202                                 String fileName, Hashtable properties)
203            throws VisADException {
204        this(descriptor, Misc.newList(fileName), properties);
205        logger.debug("SuomiNPPDataSource called, single file selected: " + fileName);
206    }
207
208    /**
209     * Construct a new Suomi NPP HDF5 data source.
210     *
211     * @param descriptor Descriptor for this {@code DataSource}.
212     * @param newSources List of filenames.
213     * @param properties Hashtable of properties.
214     *
215     * @throws VisADException problem creating data
216     */
217    
218    public SuomiNPPDataSource(DataSourceDescriptor descriptor,
219                                 List<String> newSources, Hashtable properties)
220            throws VisADException {
221        super(descriptor, newSources, DATA_DESCRIPTION, properties);
222        logger.debug("SuomiNPPDataSource constructor called, file count: " + sources.size());
223
224        filename = (String) sources.get(0);
225        setDescription("Suomi NPP");
226        
227        // NASA data is UTC, pre-set time zone
228        SimpleTimeZone stz = new SimpleTimeZone(0, "UTC");
229        sdfNASA.setTimeZone(stz);;
230        
231        // build the filename map - matches each product to set of files for that product
232        filenameMap = new HashMap<>();
233        
234        // Pass 1, populate the list of products selected
235        for (Object o : sources) {
236                String filename = (String) o;
237                // first five characters of any product go together
238                int lastSeparator = filename.lastIndexOf(File.separatorChar);
239                int firstUnderscore = filename.indexOf("_", lastSeparator + 1);
240                String prodStr = filename.substring(lastSeparator + 1, firstUnderscore);
241                if (! filenameMap.containsKey(prodStr)) {
242                                List<String> l = new ArrayList<String>();
243                                filenameMap.put(prodStr, l);
244                }
245        }
246        
247        // pass 2, create a list of files for each product in this data source
248        for (Object o : sources) {
249                String filename = (String) o;
250                // first five characters of any product go together
251                int lastSeparator = filename.lastIndexOf(File.separatorChar);
252                int firstUnderscore = filename.indexOf("_", lastSeparator + 1);
253                String prodStr = filename.substring(lastSeparator + 1, firstUnderscore);
254                List l = (List) filenameMap.get(prodStr);
255                l.add(filename);
256                filenameMap.put(prodStr, l);
257        }
258        
259        versionCheck();
260        setup();
261        initQfTranslations();
262    }
263    
264    // alert user about possible VIIRS plugin compatibility issues
265    private void versionCheck() {
266        boolean pluginDialog = getIdv().getStore().get(Constants.PREF_VIIRS_PLUGIN, false);
267        // don't create a dialog though if we are running in background/offscreen mode
268        boolean offScreen = getIdv().getArgsManager().getIsOffScreen();
269        if (! offScreen) {
270                if (! pluginDialog) {
271                        String msg = "There has been an update to the VIIRS Formulas plugin.\n" +
272                                        "If you use the plugin, you will need to uninstall the currently installed\n" +
273                                        "version of the plugin, and install the plugin called \"VIIRS Formulas\".";
274                        JCheckBox jcbPlugin = new JCheckBox("Do not show this message again");
275                        Object[] params = { msg, jcbPlugin };
276                        JOptionPane.showMessageDialog(null, params, "Plugin Compatibility Notice", JOptionPane.OK_OPTION);
277                        boolean dontShow = jcbPlugin.isSelected();
278                        getIdv().getStore().put(Constants.PREF_VIIRS_PLUGIN, dontShow);
279                }       
280        } else {
281                logger.warn("Make sure your VIIRS plugin is current, there was an update with McV 1.5");
282        }
283        }
284
285        public void setup() throws VisADException {
286
287                // which format, NASA or NOAA?
288                boolean isNOAA = false;
289                
290        // store filenames for possible bundle unpersistence
291        for (Object o : sources) {
292                oldSources.add((String) o);
293        }
294        
295        // time zone for product labels
296        SimpleTimeZone stz = new SimpleTimeZone(0, "GMT");
297        sdf.setTimeZone(stz);
298        sdfOut.setTimeZone(stz);
299        
300        // looking to populate 3 things - path to lat, path to lon, path to relevant products
301        String pathToLat = null;
302        String pathToLon = null;
303        Set<String> pathToProducts = new LinkedHashSet<>();
304        
305        // flag to differentiate VIIRS from one of the other Suomi sensors
306        boolean isVIIRS = true;
307        
308        // check source filenames to see if this is a combined product. everything
309        // from last file separator to first underscore should be product info
310        int lastSeparator = filename.lastIndexOf(File.separatorChar);
311        int firstUnderscore = filename.indexOf("_", lastSeparator + 1);
312        String prodStr = filename.substring(lastSeparator + 1, firstUnderscore);
313        // only do this check for NOAA data
314        if (filename.endsWith(".h5")) {
315                isNOAA = true;
316                StringTokenizer st = new StringTokenizer(prodStr, "-");
317                logger.debug("SNPPDS check for embedded GEO, tokenizing: " + prodStr);
318                while (st.hasMoreTokens()) {
319                        String singleProd = st.nextToken();
320                        for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) {
321                                if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) {
322                                        logger.debug("Setting isCombinedProduct true, Found embedded GEO: " + singleProd);
323                                        isCombinedProduct = true;
324                                        break;
325                                }
326                        }
327                }
328        }
329        
330        // various metatdata we'll need to gather on a per-product basis
331        Map<String, String> unsignedFlags = new LinkedHashMap<>();
332        Map<String, String> unpackFlags = new LinkedHashMap<>();
333        
334        // geo product IDs for each granule
335        Set<String> geoProductIDs = new LinkedHashSet<>();
336        
337        // aggregations will use sets of NetCDFFile readers
338        List<NetCDFFile> ncdfal = new ArrayList<>();
339        
340        // we should be able to find an XML Product Profile for each data/product type
341        SuomiNPPProductProfile nppPP = null;
342        // and also Profile metadata for geolocation variables
343        boolean haveGeoMetaData = false;
344        
345        // number of source granules which make up the data source
346        int granuleCount = 1;
347                
348        try {
349                
350                nppPP = new SuomiNPPProductProfile();
351                
352                // for each source file provided, find the appropriate geolocation,
353                // get the nominal time and various other granule-level metadata
354                Iterator keyIterator = filenameMap.keySet().iterator();
355                while (keyIterator.hasNext()) {
356                        String keyStr = (String) keyIterator.next();
357                        List fileNames = (List) filenameMap.get(keyStr);
358                        granuleCount = fileNames.size();
359                        setProperty(Constants.PROP_GRANULE_COUNT, granuleCount + " Granule");
360                        for (int fileCount = 0; fileCount < granuleCount; fileCount++) {
361                                // need to open the main NetCDF file to determine the geolocation product
362                                NetcdfFile ncfile = null;
363                                String fileAbsPath = null;
364                                try {
365                                        fileAbsPath = (String) fileNames.get(fileCount);
366                                        logger.debug("Trying to open file: " + fileAbsPath);
367                                        ncfile = NetcdfFile.open(fileAbsPath);
368                                        if (! isCombinedProduct) {
369                                                if (isNOAA) {
370                                                        Attribute a = ncfile.findGlobalAttribute("N_GEO_Ref");
371                                                        logger.debug("Value of GEO global attribute: " + a.getStringValue());
372                                                        String tmpGeoProductID = a.getStringValue();
373                                                        geoProductIDs.add(tmpGeoProductID);
374                                                } else {
375                                                        geoProductIDs.add(keyStr.replace("L1B", "GEO"));
376                                                }
377                                        }
378                                        Group rg = ncfile.getRootGroup();
379
380                                        List<Group> gl = rg.getGroups();
381                                        if (gl != null) {
382                                                for (Group g : gl) {
383                                                        logger.trace("Group name: " + g.getFullName());
384                                                        if (isNOAA) {
385                                                                        // when we find the Data_Products group, go down another group level and pull out 
386                                                                        // what we will use for nominal day and time (for now anyway).
387                                                                        // XXX TJJ fileCount check is so we don't count the GEO file in time array!
388                                                                        if (g.getFullName().contains("Data_Products")
389                                                                                        && (fileCount != fileNames.size())) {
390                                                                                List<Group> dpg = g.getGroups();
391
392                                                                                // cycle through once looking for XML Product Profiles
393                                                                                for (Group subG : dpg) {
394
395                                                                                        String subName = subG.getFullName();
396                                                                                        // use actual product, not geolocation, to id XML Product Profile
397                                                                                        if (!subName.contains("-GEO")) {
398                                                                                                // determine the instrument name (VIIRS, ATMS, CrIS, OMPS)
399                                                                                                instrumentName = subG.findAttribute("Instrument_Short_Name");
400
401                                                                                                // note any EDR products, will need to check for and remove
402                                                                                                // fill scans later
403                                                                                                Attribute adtt = subG.findAttribute("N_Dataset_Type_Tag");
404                                                                                                if (adtt != null) {
405                                                                                                        String baseName = adtt.getStringValue();
406                                                                                                        if ((baseName != null) && (baseName.equals("EDR"))) {
407                                                                                                                // have to loop through sub groups variables to determine band
408                                                                                                                List<Variable> tmpVar = subG.getVariables();
409                                                                                                                for (Variable v : tmpVar) {
410                                                                                                                        // if Imagery EDR attribute for band is specified, save it
411                                                                                                                        Attribute mBand = v.findAttribute("Band_ID");
412                                                                                                                        if (mBand != null) {
413                                                                                                                                whichEDR = mBand.getStringValue();
414                                                                                                                        }
415                                                                                                                }
416                                                                                                        }
417                                                                                                }
418
419                                                                                                // This is also where we find the attribute which tells us which
420                                                                                                // XML Product Profile to use!
421                                                                                                Attribute axpp = subG.findAttribute("N_Collection_Short_Name");
422                                                                                                if (axpp != null) {
423                                                                                                        String baseName = axpp.getStringValue();
424                                                                                                        productName = baseName;
425                                                                                                        String productProfileFileName = nppPP
426                                                                                                                        .getProfileFileName(baseName);
427                                                                                                        logger.trace("Found profile: " + productProfileFileName);
428                                                                                                        if (productProfileFileName == null) {
429                                                                                                                throw new Exception(
430                                                                                                                                "XML Product Profile not found in catalog");
431                                                                                                        }
432                                                                                                        try {
433                                                                                                                nppPP.addMetaDataFromFile(productProfileFileName);
434                                                                                                        } catch (Exception nppppe) {
435                                                                                                                logger.error("Error parsing XML Product Profile: "
436                                                                                                                                + productProfileFileName);
437                                                                                                                throw new Exception(
438                                                                                                                                "XML Product Profile Error",
439                                                                                                                                nppppe);
440                                                                                                        }
441                                                                                                }
442                                                                                        }
443                                                                                }
444
445                                                                                // 2nd pass through sub-group to extract date/time for aggregation
446                                                                                for (Group subG : dpg) {
447                                                                                        List<Variable> vl = subG.getVariables();
448                                                                                        for (Variable v : vl) {
449                                                                                                Attribute aDate = v.findAttribute("AggregateBeginningDate");
450                                                                                                Attribute aTime = v.findAttribute("AggregateBeginningTime");
451                                                                                                // did we find the attributes we are looking for?
452                                                                                                if ((aDate != null) && (aTime != null)) {
453                                                                                                        // set time for display to day/time of 1st granule examined
454                                                                                                    if (! nameHasBeenSet) {
455                                                                                                        String sDate = aDate.getStringValue();
456                                                                                                        String sTime = aTime.getStringValue();
457                                                                                                        logger.debug("For day/time, using: " + sDate
458                                                                                                                + sTime.substring(0, sTime.indexOf('Z') - 3));
459                                                                                                        Date d = sdf.parse(sDate
460                                                                                                                + sTime.substring(0, sTime.indexOf('Z') - 3));
461                                                                                                        theDate = d;
462                                                                                                        setName(instrumentName.getStringValue() + " "
463                                                                                                                + sdfOut.format(d));
464                                                                                                        nameHasBeenSet = true;
465                                                                                                    }
466                                                                                                        break;
467                                                                                                }
468                                                                                        }
469                                                                                }
470                                                                                if (! nameHasBeenSet) {
471                                                                                        throw new VisADException(
472                                                                                                        "No date time found in Suomi NPP granule");
473                                                                                }
474                                                                        } 
475                                                                } else {
476                                                                        // NASA data - date/time from global attribute
477                                                                        // set time for display to day/time of 1st granule examined
478                                                                        Attribute timeStartNASA = ncfile.findGlobalAttribute("time_coverage_start");
479                                                                        Date d = sdfNASA.parse(timeStartNASA.getStringValue());
480                                                                        theDate = d;
481                                                                        if (! nameHasBeenSet) {
482                                                                                instrumentName = ncfile.findGlobalAttribute("instrument");
483                                                                                setName(instrumentName.getStringValue() + " " + sdfOut.format(d));
484                                                                                nameHasBeenSet = true;
485                                                                        }
486                                                                }                               
487                                                }
488                                        }
489                                } catch (Exception e) {
490                                        logger.warn("Exception during processing of file: " + fileAbsPath);
491                                        throw (e);
492                                } finally {
493                                        ncfile.close();
494                                }
495                        }
496
497                }
498                
499                // build each union aggregation element
500                Iterator<String> iterator = geoProductIDs.iterator();
501                for (int elementNum = 0; elementNum < granuleCount; elementNum++) {
502                        
503                        String s = null;
504                        
505                        // build an XML (NCML actually) representation of the union aggregation of these two files
506                        Namespace ns = Namespace.getNamespace("http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2");
507                        Element root = new Element("netcdf", ns);
508                        Document document = new Document(root);
509
510                        Element agg = new Element("aggregation", ns);
511                        agg.setAttribute("type", "union");
512                        
513                        // TJJ - Loop over filename map, could be several products that need to be aggregated
514                Set set = filenameMap.keySet();
515                Iterator mapIter = set.iterator();
516                while (mapIter.hasNext()) {
517                        String key = (String) mapIter.next();
518                        List l = (List) filenameMap.get(key);
519                                Element fData = new Element("netcdf", ns);
520                                fData.setAttribute("location", (String) l.get(elementNum));
521                                agg.addContent(fData);
522                                s = (String) l.get(elementNum);
523                }
524                        
525                        String geoFilename = null;
526                        Element fGeo = new Element("netcdf", ns);;
527                        
528                        if (! isCombinedProduct) {
529        
530                                if (isNOAA) {
531                                                geoFilename = s.substring(0,
532                                                                s.lastIndexOf(File.separatorChar) + 1);
533                                                // check if we have the whole file name or just the prefix
534                                                String geoProductID = iterator.next();
535                                                if (geoProductID.endsWith("h5")) {
536                                                        geoFilename += geoProductID;
537                                                } else {
538                                                        geoFilename += geoProductID;
539                                                        geoFilename += s.substring(s
540                                                                        .lastIndexOf(File.separatorChar) + 6);
541                                                }
542                                                // Be sure file as specified by N_GEO_Ref global attribute really is there.
543                                                File tmpGeo = new File(geoFilename);
544                                                if (!tmpGeo.exists()) {
545                                                        // Ok, the expected file defined (supposedly) exactly by a global att is not there...
546                                                        // We need to check for similar geo files with different creation dates
547                                                        String geoFileRelative = geoFilename
548                                                                        .substring(geoFilename
549                                                                                        .lastIndexOf(File.separatorChar) + 1);
550                                                        // also check for Terrain Corrected version of geo
551                                                        String geoTerrainCorrected = geoFileRelative;
552                                                        geoTerrainCorrected = geoTerrainCorrected.replace(
553                                                                        "OD", "TC");
554                                                        geoTerrainCorrected = geoTerrainCorrected.replace(
555                                                                        "MG", "TC");
556
557                                                        // now we make a file filter, and see if a matching geo file is present
558                                                        File fList = new File(
559                                                                        geoFilename.substring(
560                                                                                        0,
561                                                                                        geoFilename
562                                                                                                        .lastIndexOf(File.separatorChar) + 1)); // current directory
563
564                                                        FilenameFilter geoFilter = new FilenameFilter() {
565                                                                public boolean accept(File dir, String name) {
566                                                                        if (name.matches(JPSSUtilities.SUOMI_GEO_REGEX_NOAA)) {
567                                                                                return true;
568                                                                        } else {
569                                                                                return false;
570                                                                        }
571                                                                }
572                                                        };
573
574                                                        File[] files = fList.listFiles(geoFilter);
575                                                        for (File file : files) {
576                                                                if (file.isDirectory()) {
577                                                                        continue;
578                                                                }
579                                                                // get the file name for convenience
580                                                                String fName = file.getName();
581                                                                // is it one of the standard Ellipsoid geo types we are looking for?
582                                                                if (fName.substring(0, 5).equals(
583                                                                                geoFileRelative.substring(0, 5))) {
584                                                                        int geoStartIdx = geoFileRelative
585                                                                                        .indexOf("_d");
586                                                                        int prdStartIdx = fName.indexOf("_d");
587                                                                        String s1 = geoFileRelative.substring(
588                                                                                        geoStartIdx, geoStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX);
589                                                                        String s2 = fName.substring(prdStartIdx,
590                                                                                        prdStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX);
591                                                                        if (s1.equals(s2)) {
592                                                                                geoFilename = s
593                                                                                                .substring(
594                                                                                                                0,
595                                                                                                                s.lastIndexOf(File.separatorChar) + 1)
596                                                                                                + fName;
597                                                                                break;
598                                                                        }
599                                                                }
600                                                                // same check, but for Terrain Corrected version
601                                                                if (fName.substring(0, 5).equals(
602                                                                                geoTerrainCorrected.substring(0, 5))) {
603                                                                        int geoStartIdx = geoTerrainCorrected
604                                                                                        .indexOf("_d");
605                                                                        int prdStartIdx = fName.indexOf("_d");
606                                                                        String s1 = geoTerrainCorrected.substring(
607                                                                                        geoStartIdx, geoStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX);
608                                                                        String s2 = fName.substring(prdStartIdx,
609                                                                                        prdStartIdx + JPSSUtilities.NOAA_CREATION_DATE_INDEX);
610                                                                        if (s1.equals(s2)) {
611                                                                                geoFilename = s
612                                                                                                .substring(
613                                                                                                                0,
614                                                                                                                s.lastIndexOf(File.separatorChar) + 1)
615                                                                                                + fName;
616                                                                                break;
617                                                                        }
618                                                                }
619                                                        }
620                                                } 
621                                        } else {
622                                                // NASA format
623                                                geoFilename = JPSSUtilities.replaceLast(s, "L1B", "GEO");
624                                                // get list of files in current directory
625                                                File fList = 
626                                                        new File(geoFilename.substring(0, geoFilename.lastIndexOf(File.separatorChar) + 1)); 
627                                                // make a NASA style file filter, and see if a matching geo file is present
628                                                FilenameFilter geoFilter = new FilenameFilter() {
629                                                        public boolean accept(File dir, String name) {
630                                                                if (name.matches(JPSSUtilities.SUOMI_GEO_REGEX_NASA)) {
631                                                                        return true;
632                                                                } else {
633                                                                        return false;
634                                                                }
635                                                        }
636                                                };
637                                                File[] files = fList.listFiles(geoFilter);
638                                                for (File file : files) {
639                                                        if (file.isDirectory()) {
640                                                                continue;
641                                                        }
642                                                        // get the file name for convenience
643                                                        String fName = file.getName();
644                                                        String tmpStr = geoFilename.substring(s.lastIndexOf(File.separatorChar) + 1,
645                                                                        s.lastIndexOf(File.separatorChar) + (JPSSUtilities.NASA_CREATION_DATE_INDEX + 1));
646                                                        if (fName.substring(0, JPSSUtilities.NASA_CREATION_DATE_INDEX).equals(tmpStr.substring(0, JPSSUtilities.NASA_CREATION_DATE_INDEX))) {
647                                                                geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1) + fName;
648                                                                break;
649                                                        }
650                                                }
651                                        }
652                                        logger.debug("Determined GEO file name should be: " + geoFilename);
653                                fGeo.setAttribute("location", geoFilename);
654                                // add this to list used if we create a zipped bundle
655                                geoSources.add(geoFilename);
656                                agg.addContent(fGeo);
657                        }
658
659                        root.addContent(agg);    
660                    XMLOutputter xmlOut = new XMLOutputter();
661                    String ncmlStr = xmlOut.outputString(document);
662                    ByteArrayInputStream is = new ByteArrayInputStream(ncmlStr.getBytes());                     
663                    MultiDimensionReader netCDFReader = new NetCDFFile(is);
664                    
665                // let's try and look through the NetCDF reader and see what we can learn...
666                NetcdfFile ncdff = ((NetCDFFile) netCDFReader).getNetCDFFile();
667                
668                Group rg = ncdff.getRootGroup();
669                // this is a list filled with unpacked qflag products, if any
670                ArrayList<VariableDS> qfProds = new ArrayList<VariableDS>();
671                
672                // this is a list filled with pseudo Brightness Temp variables converted from Radiance
673                ArrayList<VariableDS> btProds = new ArrayList<VariableDS>();
674
675                List<Group> gl = rg.getGroups();
676                if (gl != null) {
677                                int xDimNASA = -1;
678                                int yDimNASA = -1;
679                        // Make a first pass to determine the shape of the geolocation data
680                        for (Group g : gl) {
681                                if (g.getFullName().contains("geolocation_data")) {
682                                        List<Variable> vl = g.getVariables();
683                                                for (Variable v : vl) {
684                                                        if (v.getShortName().equals("latitude")) {
685                                                                // XXX TJJ Nov 2015
686                                                                // Hack because fill value in attribute does not match
687                                                                // what I am seeing in the data.
688                                                                Attribute fillAtt = new Attribute("_FillValue", -999.0);
689                                                                v.addAttribute(fillAtt);
690                                                                pathToLat = v.getFullName();
691                                                                pathToProducts.add(v.getFullName());
692                                                                xDimNASA = v.getDimension(0).getLength();
693                                                                yDimNASA = v.getDimension(1).getLength();
694                                                        }
695                                                        if (v.getShortName().equals("longitude")) {
696                                                                // XXX TJJ Nov 2015
697                                                                // Hack because fill value in attribute does not match
698                                                                // what I am seeing in the data.
699                                                                Attribute fillAtt = new Attribute("_FillValue", -999.0);
700                                                                v.addAttribute(fillAtt);
701                                                                pathToLon = v.getFullName();
702                                                                pathToProducts.add(v.getFullName());
703                                                        }
704                                                }
705                                }
706                        }
707                        for (Group g : gl) {
708                                logger.debug("Group name: " + g.getFullName());
709                                // NASA only - looking through observation_data and geolocation_data
710                                if (g.getFullName().contains("observation_data")) {
711                                        List<Variable> vl = g.getVariables();
712                                                for (Variable v : vl) {
713                                                        // keep any data which matches geolocation dimensions
714                                                        if (v.getDimension(0).getLength() == xDimNASA &&
715                                                                v.getDimension(1).getLength() == yDimNASA) {
716                                                                logger.debug("Adding product: " + v.getFullName());
717                                                                pathToProducts.add(v.getFullName());
718                                                                
719                                                                Attribute aUnsigned = v.findAttribute("_Unsigned");
720                                                                if (aUnsigned != null) {
721                                                                        unsignedFlags.put(v.getFullName(), aUnsigned.getStringValue());
722                                                                } else {
723                                                                        unsignedFlags.put(v.getFullName(), "false");
724                                                                }
725                                                                
726                                                                // store units in a map for later
727                                                                Attribute unitAtt = v.findAttribute("units");
728                                                                if (unitAtt != null) {
729                                                                        unitsNASA.put(v.getShortName(), unitAtt.getStringValue());
730                                                                } else {
731                                                                        unitsNASA.put(v.getShortName(), "Unknown");
732                                                                }
733                                                                
734                                                                // TJJ Feb 2016 - Create BT variables where applicable
735                                                                if ((v.getShortName().matches("M12|M13|M14|M15|M16")) ||
736                                                                        (v.getShortName().matches("I04|I05"))) {
737                                                                        
738                                                                        // Get the LUT variable, load into primitive array
739                                                                        Variable lut = g.findVariable(v.getShortName() + "_brightness_temperature_lut");
740                                                                        int [] lutShape = lut.getShape();
741                                                                        logger.debug("Handling NASA LUT Variable, LUT size: " + lutShape[0]);
742                                                                        
743                                                                        // pull out valid min, max - these will be used for our new VariableDS
744                                                                        Attribute aVMin = lut.findAttribute("valid_min");
745                                                                        Attribute aVMax = lut.findAttribute("valid_max");
746                                                                        Attribute fillAtt = lut.findAttribute("_FillValue");
747                                                                        logger.debug("valid_min from LUT: " + aVMin.getNumericValue());
748                                                                        logger.debug("valid_max from LUT: " + aVMax.getNumericValue());
749                                                                        
750                                                                        // A little hacky, but at this point the class is such a mess
751                                                                        // that what's a little more, right? Load M12-M16, I4-I5 LUTS
752                                                                        
753                                                                        if (v.getShortName().matches("M12")) {
754                                                                                m12LUT = new float[lutShape[0]];
755                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
756                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
757                                                                                        m12LUT[lutIdx] = lutArray.get(lutIdx);
758                                                                                }
759                                                                        }
760                                                                        
761                                                                        if (v.getShortName().matches("M13")) {
762                                                                                m13LUT = new float[lutShape[0]];
763                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
764                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
765                                                                                        m13LUT[lutIdx] = lutArray.get(lutIdx);
766                                                                                }
767                                                                        }
768                                                                        
769                                                                        if (v.getShortName().matches("M14")) {
770                                                                                m14LUT = new float[lutShape[0]];
771                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
772                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
773                                                                                        m14LUT[lutIdx] = lutArray.get(lutIdx);
774                                                                                }
775                                                                        }
776                                                                        
777                                                                        if (v.getShortName().matches("M15")) {
778                                                                                m15LUT = new float[lutShape[0]];
779                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
780                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
781                                                                                        m15LUT[lutIdx] = lutArray.get(lutIdx);
782                                                                                }
783                                                                        }
784                                                                        
785                                                                        if (v.getShortName().matches("M16")) {
786                                                                                m16LUT = new float[lutShape[0]];
787                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
788                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
789                                                                                        m16LUT[lutIdx] = lutArray.get(lutIdx);
790                                                                                }
791                                                                        }
792                                                                        
793                                                                        if (v.getShortName().matches("I04")) {
794                                                                                i04LUT = new float[lutShape[0]];
795                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
796                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
797                                                                                        i04LUT[lutIdx] = lutArray.get(lutIdx);
798                                                                                }
799                                                                        }
800                                                                        
801                                                                        if (v.getShortName().matches("I05")) {
802                                                                                i05LUT = new float[lutShape[0]];
803                                                                                ArrayFloat.D1 lutArray = (ArrayFloat.D1) lut.read();
804                                                                                for (int lutIdx = 0; lutIdx < lutShape[0]; lutIdx++) {
805                                                                                        i05LUT[lutIdx] = lutArray.get(lutIdx);
806                                                                                }
807                                                                        }
808                                                                        
809                                                                        // Create a pseudo-variable, fill using LUT
810                                                                        // make a copy of the source variable
811                                                                        // NOTE: by using a VariableDS here, the original
812                                                                        // variable is used for the I/O, this matters!
813                                                                        VariableDS vBT = new VariableDS(g, v, false);
814
815                                                                        // Name is orig name plus suffix
816                                                                        vBT.setShortName(v.getShortName() + "_BT");
817                                                                        
818                                                                        vBT.addAttribute(fillAtt);
819                                                                        vBT.addAttribute(aVMin);
820                                                                        vBT.addAttribute(aVMax);
821
822                                                                        if (v.getShortName().matches("M12")) {
823                                                                                lutMap.put(vBT.getFullName(), m12LUT);
824                                                                        }
825                                                                        if (v.getShortName().matches("M13")) {
826                                                                                lutMap.put(vBT.getFullName(), m13LUT);
827                                                                        }
828                                                                        if (v.getShortName().matches("M14")) {
829                                                                                lutMap.put(vBT.getFullName(), m14LUT);
830                                                                        }
831                                                                        if (v.getShortName().matches("M15")) {
832                                                                                lutMap.put(vBT.getFullName(), m15LUT);
833                                                                        }
834                                                                        if (v.getShortName().matches("M16")) {
835                                                                                lutMap.put(vBT.getFullName(), m16LUT);
836                                                                        }
837                                                                        if (v.getShortName().matches("I04")) {
838                                                                                lutMap.put(vBT.getFullName(), i04LUT);
839                                                                        }
840                                                                        if (v.getShortName().matches("I05")) {
841                                                                                lutMap.put(vBT.getFullName(), i05LUT);
842                                                                        }
843                                                                        pathToProducts.add(vBT.getFullName());
844                                                                        btProds.add(vBT);
845                                                                }
846                                                        }
847                                                }
848                                }
849                                if (g.getFullName().contains("geolocation_data")) {
850                                        List<Variable> vl = g.getVariables();
851                                                for (Variable v : vl) {
852                                                        // keep any data which matches geolocation dimensions
853                                                        if (v.getDimension(0).getLength() == xDimNASA &&
854                                                                v.getDimension(1).getLength() == yDimNASA) {
855                                                                // except we already found Lat and Lon, skip those 
856                                                                if ((v.getShortName().equals("latitude")) ||
857                                                                    (v.getShortName().equals("latitude"))) continue;
858                                                                logger.debug("Adding product: " + v.getFullName());
859                                                                pathToProducts.add(v.getFullName());
860                                                        }
861                                                }
862                                }
863                                
864                                // NOAA only - we are looking through All_Data, finding displayable data
865                                if (g.getFullName().contains("All_Data")) {
866                                        List<Group> adg = g.getGroups();
867                                        int xDim = -1;
868                                        int yDim = -1;
869
870                                        // two sub-iterations, first one to find geolocation and product dimensions
871                                        for (Group subG : adg) {
872                                                logger.debug("Sub group name: " + subG.getFullName());
873                                                String subName = subG.getFullName();
874                                                if (subName.contains("-GEO")) {
875                                                        // this is the geolocation data
876                                                        String geoBaseName = subG.getShortName();
877                                                        geoBaseName = geoBaseName.substring(0, geoBaseName.indexOf('_'));
878                                                        if (! haveGeoMetaData) {
879                                                                String geoProfileFileName = nppPP.getProfileFileName(geoBaseName);
880                                                                        // also add meta data from geolocation profile
881                                                                        nppPP.addMetaDataFromFile(geoProfileFileName);
882                                                                haveGeoMetaData = true;
883                                                        }
884                                                        List<Variable> vl = subG.getVariables();
885                                                        for (Variable v : vl) {
886                                                                if (v.getFullName().endsWith(SEPARATOR_CHAR + "Latitude")) {
887                                                                        pathToLat = v.getFullName();
888                                                                        logger.debug("Ellipsoid Lat/Lon Variable: " + v.getFullName());
889                                                                        // get the dimensions of the lat variable
890                                                                        Dimension dAlongTrack = v.getDimension(0);
891                                                                        yDim = dAlongTrack.getLength();
892                                                                        Dimension dAcrossTrack = v.getDimension(1);
893                                                                        xDim = dAcrossTrack.getLength();
894                                                                        logger.debug("Lat across track dim: " + dAcrossTrack.getLength());
895                                                                }
896                                                                if (v.getFullName().endsWith(SEPARATOR_CHAR + "Longitude")) {
897                                                                        // we got dimensions from lat, don't need 'em twice, but need path
898                                                                        pathToLon = v.getFullName();
899                                                                }
900                                                        } 
901                                                        // one more pass in case there is terrain-corrected Lat/Lon
902                                                        for (Variable v : vl) {
903                                                                if (v.getFullName().endsWith(SEPARATOR_CHAR + "Latitude_TC")) {
904                                                                        pathToLat = v.getFullName();
905                                                                        logger.debug("Switched Lat/Lon Variable to TC: " + v.getFullName());
906                                                                        // get the dimensions of the lat variable
907                                                                        Dimension dAlongTrack = v.getDimension(0);
908                                                                        yDim = dAlongTrack.getLength();
909                                                                        Dimension dAcrossTrack = v.getDimension(1);
910                                                                        xDim = dAcrossTrack.getLength();
911                                                                        logger.debug("Lat across track dim: " + dAcrossTrack.getLength());
912                                                                }
913                                                                if (v.getFullName().endsWith(SEPARATOR_CHAR + "Longitude_TC")) {
914                                                                        // we got dimensions from lat, don't need 'em twice, but need path
915                                                                        pathToLon = v.getFullName();
916                                                                }
917                                                        }
918                                                }
919                                        }
920
921                                        // second to identify displayable products
922                                        for (Group subG : adg) {
923                                                // this is the product data
924                                                List<Variable> vl = subG.getVariables();
925                                                for (Variable v : vl) {
926                                                        boolean useThis = false;
927                                                        String vName = v.getFullName();
928                                                        logger.trace("Variable: " + vName);
929                                                        String varShortName = vName.substring(vName.lastIndexOf(SEPARATOR_CHAR) + 1);
930
931                                                        // Special code to handle quality flags. We throw out anything
932                                                        // that does not match bounds of the geolocation data
933                                                        
934                                                        if (varShortName.startsWith("QF")) {
935                                                                
936                                                                logger.trace("Handling Quality Flag: " + varShortName);
937                                                                
938                                                                // this check is done later for ALL variables, but we need
939                                                                // it early here to weed out those quality flags that are 
940                                                                // simply a small set of data w/no granule geo nbounds
941                                                                boolean xScanOk = false;
942                                                                boolean yScanOk = false;
943                                                                List<Dimension> dl = v.getDimensions();
944                                                                
945                                                                // toss out > 2D Quality Flags 
946                                                                if (dl.size() > 2) {
947                                                                        logger.trace("SKIPPING QF, > 2D: " + varShortName);
948                                                                        continue;
949                                                                }
950                                                                
951                                                                for (Dimension d : dl) {
952                                                                        // in order to consider this a displayable product, make sure
953                                                                        // both scan direction dimensions are present and look like a granule
954                                                                        if (d.getLength() == xDim) {
955                                                                                xScanOk = true;
956                                                                        }
957                                                                        if (d.getLength() == yDim) {
958                                                                                yScanOk = true;
959                                                                        }
960                                                                }
961                                                                
962                                                                if (! (xScanOk && yScanOk)) {
963                                                                        logger.trace("SKIPPING QF, does not match geo bounds: " + varShortName);
964                                                                        continue;
965                                                                }
966                                                                
967                                                                ArrayList<QualityFlag> qfal = nppPP.getQualityFlags(varShortName);
968                                                                if (qfal != null) {
969                                                                        for (QualityFlag qf : qfal) {
970                                                                                qf.setPackedName(vName);
971                                                                                // make a copy of the qflag variable
972                                                                                // NOTE: by using a VariableDS here, the original
973                                                                                // variable is used for the I/O, this matters!
974                                                                                VariableDS vqf = new VariableDS(subG, v, false);
975                                                                                // prefix with QF num to help guarantee uniqueness across groups
976                                                                                // this will cover most cases, but could still be dupe names
977                                                                                // within a single QF.  This is handled when fetching XMLPP metadata
978                                                                                vqf.setShortName(
979                                                                                                varShortName.substring(0, 3) + "_" + qf.getName()
980                                                                                );
981                                                                                logger.debug("New QF var full name: " + vqf.getFullName());
982                                                                        qfProds.add(vqf);
983                                                                        qfMap.put(vqf.getFullName(), qf);
984                                                                        }
985                                                                }
986                                                        }
987
988                                                        // for CrIS instrument, first find dimensions of var matching
989                                                        // CrIS filter, then throw out all variables which don't match 
990                                                        // those dimensions
991                                                        
992                                                        if (instrumentName.getStringValue().equals("CrIS")) {
993                                                                if (! vName.contains("GEO")) {
994                                                                        if (! varShortName.startsWith(crisFilter)) {
995                                                                                logger.trace("Skipping variable: " + varShortName);
996                                                                                continue;
997                                                                        }
998                                                                } else {
999                                                                        // these variables are all GEO-related
1000                                                                        // if they match lat/lon bounds, keep them
1001                                                                        List<Dimension> dl = v.getDimensions();
1002                                                                        if (dl.size() == 3) {
1003                                                                                boolean isDisplayableCrIS = true;
1004                                                                                for (Dimension d : dl) {
1005                                                                                        if ((d.getLength() != xDim) && (d.getLength() != yDim) && (d.getLength() != 9)) {
1006                                                                                                isDisplayableCrIS = false;
1007                                                                                        }
1008                                                                                }
1009                                                                                if (! isDisplayableCrIS) {
1010                                                                                        continue;
1011                                                                                }
1012                                                                        }
1013                                                                }
1014                                                        }
1015
1016                                                        // for OMPS, only Radiance for now...
1017                                                        if (instrumentName.getStringValue().contains("OMPS")) {
1018                                                                if (! varShortName.startsWith(ompsFilter)) {
1019                                                                        logger.trace("Skipping OMPS variable: " + varShortName);
1020                                                                        continue;
1021                                                                }
1022                                                        }
1023
1024                                                        DataType dt = v.getDataType();
1025                                                        if ((dt.getSize() != 4) && (dt.getSize() != 2) && (dt.getSize() != 1)) {
1026                                                                continue;
1027                                                        }
1028
1029                                                        List<Dimension> dl = v.getDimensions();
1030                                                        if (dl.size() > 4) {
1031                                                                continue;
1032                                                        }
1033
1034                                                        // for now, skip any 3D VIIRS data
1035                                                        if (instrumentName.getStringValue().equals("VIIRS")) {
1036                                                                if (dl.size() == 3) {
1037                                                                        continue;
1038                                                                }
1039                                                        }
1040
1041                                                        boolean xScanOk = false;
1042                                                        boolean yScanOk = false;
1043                                                        for (Dimension d : dl) {
1044                                                                // in order to consider this a displayable product, make sure
1045                                                                // both scan direction dimensions are present and look like a granule
1046                                                                if (d.getLength() == xDim) {
1047                                                                        xScanOk = true;
1048                                                                }
1049                                                                if (d.getLength() == yDim) {
1050                                                                        yScanOk = true;
1051                                                                }
1052                                                        }
1053
1054                                                        if (xScanOk && yScanOk) {
1055                                                                useThis = true;
1056                                                        }
1057
1058                                                        // For ATMS, only 3-D variable we pass through is BrightnessTemperature
1059                                                        // Dimensions for BT are (lon, lat, channel)
1060                                                        if (instrumentName.getStringValue().equals("ATMS")) {
1061                                                                if (dl.size() == 3) {
1062                                                                        boolean isDisplayableATMS = false;
1063                                                                        for (Dimension d : dl) {
1064                                                                                if (d.getLength() == JPSSUtilities.ATMSChannelCenterFrequencies.length) {
1065                                                                                        isDisplayableATMS = true;
1066                                                                                        logger.trace("This variable has a dimension matching num ATMS channels");
1067                                                                                        break;
1068                                                                                }
1069                                                                        }
1070                                                                        if (! isDisplayableATMS) useThis = false;
1071                                                                }
1072                                                        }
1073
1074                                                        // sensor data with a channel dimension
1075                                                        if (useThis) {
1076                                                                if ((instrumentName.getStringValue().equals("CrIS")) ||
1077                                                                                (instrumentName.getStringValue().equals("ATMS")) || 
1078                                                                                (instrumentName.getStringValue().contains("OMPS"))) {
1079                                                                        isVIIRS = false;
1080                                                                        logger.debug("Handling non-VIIRS data source...");
1081                                                                }
1082                                                        }
1083
1084                                                        if (useThis) { 
1085                                                                // loop through the variable list again, looking for a corresponding "Factors"
1086                                                                float scaleVal = 1f;
1087                                                                float offsetVal = 0f;
1088                                                                boolean unpackFlag = false;
1089
1090                                                                //   if the granule has an entry for this variable name
1091                                                                //     get the data, data1 = scale, data2 = offset
1092                                                                //     create and poke attributes with this data
1093                                                                //   endif
1094
1095                                                                String factorsVarName = nppPP.getScaleFactorName(varShortName);
1096                                                                logger.debug("Mapping: " + varShortName + " to: " + factorsVarName);
1097                                                                if (factorsVarName != null) {
1098                                                                        for (Variable fV : vl) {
1099                                                                                if (fV.getShortName().equals(factorsVarName)) {
1100                                                                                        logger.trace("Pulling scale and offset values from variable: " + fV.getShortName());
1101                                                                                        ucar.ma2.Array a = fV.read();
1102                                                                                        float[] so = (float[]) a.copyTo1DJavaArray();
1103                                                                                        scaleVal = so[0];
1104                                                                                        offsetVal = so[1];
1105                                                                                        logger.trace("Scale value: " + scaleVal + ", Offset value: " + offsetVal);
1106                                                                                        unpackFlag = true;
1107                                                                                        break;
1108                                                                                }
1109                                                                        }
1110                                                                }
1111
1112                                                                // poke in scale/offset attributes for now
1113
1114                                                                Attribute a1 = new Attribute("scale_factor", scaleVal);
1115                                                                v.addAttribute(a1);
1116                                                                Attribute a2 = new Attribute("add_offset", offsetVal);
1117                                                                v.addAttribute(a2);  
1118
1119                                                                // add valid range and fill value attributes here
1120                                                                // try to fill in valid range
1121                                                                if (nppPP.hasNameAndMetaData(varShortName)) {
1122                                                                        String rangeMin = nppPP.getRangeMin(varShortName);
1123                                                                        String rangeMax = nppPP.getRangeMax(varShortName);
1124                                                                        logger.trace("range min: " + rangeMin + ", range max: " + rangeMax);
1125                                                                        // only store range attribute if VALID range found
1126                                                                        if ((rangeMin != null) && (rangeMax != null)) {
1127                                                                                int [] shapeArr = new int [] { 2 };
1128                                                                                ArrayFloat af = new ArrayFloat(shapeArr);
1129                                                                                try {
1130                                                                                        af.setFloat(0, Float.parseFloat(rangeMin));
1131                                                                                } catch (NumberFormatException nfe) {
1132                                                                                        af.setFloat(0, new Float(Integer.MIN_VALUE));
1133                                                                                }
1134                                                                                try {
1135                                                                                        af.setFloat(1, Float.parseFloat(rangeMax));
1136                                                                                } catch (NumberFormatException nfe) {
1137                                                                                        af.setFloat(1, new Float(Integer.MAX_VALUE));
1138                                                                                }
1139                                                                                Attribute rangeAtt = new Attribute("valid_range", af);
1140                                                                                v.addAttribute(rangeAtt);
1141                                                                        }
1142
1143                                                                        // check for and load fill values too...
1144
1145                                                                        // we need to check two places, first, the XML product profile
1146                                                                        ArrayList<Float> fval = nppPP.getFillValues(varShortName);
1147
1148                                                                        // 2nd, does the variable already have one defined?
1149                                                                        // if there was already a fill value associated with this variable, make
1150                                                                        // sure we bring that along for the ride too...
1151                                                                        Attribute aFill = v.findAttribute("_FillValue");
1152
1153                                                                        // determine size of our fill value array
1154                                                                        int fvArraySize = 0;
1155                                                                        if (aFill != null) fvArraySize++;
1156                                                                        if (! fval.isEmpty()) fvArraySize += fval.size();
1157                                                                        int [] fillShape = new int [] { fvArraySize };
1158
1159                                                                        // allocate the array
1160                                                                        ArrayFloat afFill = new ArrayFloat(fillShape);
1161
1162                                                                        // and FINALLY, fill it!
1163                                                                        if (! fval.isEmpty()) {
1164                                                                                for (int fillIdx = 0; fillIdx < fval.size(); fillIdx++) {
1165                                                                                        afFill.setFloat(fillIdx, fval.get(fillIdx));
1166                                                                                        logger.trace("Adding fill value (from XML): " + fval.get(fillIdx));
1167                                                                                }
1168                                                                        }
1169
1170                                                                        if (aFill != null) {
1171                                                                                Number n = aFill.getNumericValue();
1172                                                                                // is the data unsigned?
1173                                                                                Attribute aUnsigned = v.findAttribute("_Unsigned");
1174                                                                                float fillValAsFloat = Float.NaN;
1175                                                                                if (aUnsigned != null) {
1176                                                                                        if (aUnsigned.getStringValue().equals("true")) {
1177                                                                                                DataType fvdt = aFill.getDataType();
1178                                                                                                logger.trace("Data String: " + aFill.toString());
1179                                                                                                logger.trace("DataType primitive type: " + fvdt.getPrimitiveClassType());
1180                                                                                                // signed byte that needs conversion?
1181                                                                                                if (fvdt.getPrimitiveClassType() == byte.class) {
1182                                                                                                        fillValAsFloat = (float) Util.unsignedByteToInt(n.byteValue());
1183                                                                                                }
1184                                                                                                else if (fvdt.getPrimitiveClassType() == short.class) {
1185                                                                                                        fillValAsFloat = (float) Util.unsignedShortToInt(n.shortValue());
1186                                                                                                } else {
1187                                                                                                        fillValAsFloat = n.floatValue();
1188                                                                                                }
1189                                                                                        }
1190                                                                                }
1191                                                                                afFill.setFloat(fvArraySize - 1, fillValAsFloat);
1192                                                                                logger.trace("Adding fill value (from variable): " + fillValAsFloat);
1193                                                                        }
1194                                                                        Attribute fillAtt = new Attribute("_FillValue", afFill);
1195                                                                        v.addAttribute(fillAtt);
1196                                                                }
1197
1198                                                                Attribute aUnsigned = v.findAttribute("_Unsigned");
1199                                                                if (aUnsigned != null) {
1200                                                                        unsignedFlags.put(v.getFullName(), aUnsigned.getStringValue());
1201                                                                } else {
1202                                                                        unsignedFlags.put(v.getFullName(), "false");
1203                                                                }
1204
1205                                                                if (unpackFlag) {
1206                                                                        unpackFlags.put(v.getFullName(), "true");
1207                                                                } else {
1208                                                                        unpackFlags.put(v.getFullName(), "false");
1209                                                                }
1210
1211                                                                logger.debug("Adding product: " + v.getFullName());
1212                                                                pathToProducts.add(v.getFullName());
1213
1214                                                        }
1215                                                }
1216                                        }
1217                                }
1218                        }
1219                }
1220                
1221                // add in any unpacked qflag products
1222                for (VariableDS qfV: qfProds) {
1223                        // skip the spares - they are reserved for future use
1224                        if (qfV.getFullName().endsWith("Spare")) {
1225                                continue;
1226                        }
1227                        // String.endsWith is case sensitive so gotta check both cases
1228                        if (qfV.getFullName().endsWith("spare")) {
1229                                continue;
1230                        }
1231                        ncdff.addVariable(qfV.getGroup(), qfV);
1232                        logger.trace("Adding QF product: " + qfV.getFullName());
1233                        pathToProducts.add(qfV.getFullName());
1234                        unsignedFlags.put(qfV.getFullName(), "true");
1235                        unpackFlags.put(qfV.getFullName(), "false");
1236                }
1237                
1238                // add in any pseudo BT products from NASA data
1239                for (Variable vBT: btProds) {
1240                        logger.trace("Adding BT product: " + vBT.getFullName());
1241                                        ncdff.addVariable(vBT.getGroup(), vBT);
1242                                        unsignedFlags.put(vBT.getFullName(), "true");
1243                                        unpackFlags.put(vBT.getFullName(), "false");
1244                }
1245                
1246                    ncdfal.add((NetCDFFile) netCDFReader);
1247                }
1248                
1249        } catch (Exception e) {
1250                logger.error("cannot create NetCDF reader for files selected", e);
1251                if (e.getMessage() != null && e.getMessage().equals("XML Product Profile Error")) {
1252                        throw new VisADException("Unable to extract metadata from required XML Product Profile", e);
1253                }
1254        }
1255        
1256        // initialize the aggregation reader object
1257        try {
1258                if (isNOAA) {
1259                    nppAggReader = new GranuleAggregation(ncdfal, pathToProducts, "Track", "XTrack", isVIIRS);
1260                    ((GranuleAggregation) nppAggReader).setQfMap(qfMap);
1261                } else {
1262                        nppAggReader = new GranuleAggregation(ncdfal, pathToProducts, "number_of_lines", "number_of_pixels", isVIIRS);
1263                    ((GranuleAggregation) nppAggReader).setLUTMap(lutMap);
1264                }
1265        } catch (Exception e) {
1266                throw new VisADException("Unable to initialize aggregation reader", e);
1267        }
1268
1269        // make sure we found valid data
1270        if (pathToProducts.size() == 0) {
1271                throw new VisADException("No data found in files selected");
1272        }
1273        
1274        logger.debug("Number of adapters needed: " + pathToProducts.size());
1275        adapters = new MultiDimensionAdapter[pathToProducts.size()];
1276        Hashtable<String, String[]> properties = new Hashtable<>();
1277        
1278        Iterator<String> iterator = pathToProducts.iterator();
1279        int pIdx = 0;
1280        boolean adapterCreated = false;
1281        while (iterator.hasNext()) {
1282                String pStr = iterator.next();
1283                logger.debug("Working on adapter number " + (pIdx + 1) + ": " + pStr);
1284                Map<String, Object> swathTable = SwathAdapter.getEmptyMetadataTable();
1285                Map<String, Object> spectTable = SpectrumAdapter.getEmptyMetadataTable();
1286                swathTable.put("array_name", pStr);
1287                swathTable.put("lon_array_name", pathToLon);
1288                swathTable.put("lat_array_name", pathToLat);
1289                swathTable.put("XTrack", "XTrack");
1290                swathTable.put("Track", "Track");
1291                swathTable.put("geo_Track", "Track");
1292                swathTable.put("geo_XTrack", "XTrack");
1293                // TJJ is this even needed?  Is product_name used anywhere?
1294                if (productName == null) productName = pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1);
1295                swathTable.put("product_name", productName);
1296                
1297                // array_name common to spectrum table
1298                spectTable.put("array_name", pStr);
1299                spectTable.put("product_name", productName);
1300                
1301                if (! isVIIRS) {
1302
1303                        // 3D data is either ATMS, OMPS, or CrIS
1304                        if ((instrumentName.getShortName() != null) && (instrumentName.getStringValue().equals("ATMS"))) {
1305
1306                                spectTable.put(SpectrumAdapter.channelIndex_name, "Channel");
1307                        swathTable.put(SpectrumAdapter.channelIndex_name, "Channel");
1308                        
1309                        swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
1310                        swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
1311                        swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
1312                        spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
1313                        spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
1314                        spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
1315                        
1316                        spectTable.put(SpectrumAdapter.channelType, "wavelength");
1317                        spectTable.put(SpectrumAdapter.channels_name, "Channel");
1318                    spectTable.put(SpectrumAdapter.x_dim_name, "XTrack");
1319                    spectTable.put(SpectrumAdapter.y_dim_name, "Track");
1320                    
1321                                int numChannels = JPSSUtilities.ATMSChannelCenterFrequencies.length;
1322                        float[] bandArray = new float[numChannels];
1323                        String[] bandNames = new String[numChannels];
1324                        for (int bIdx = 0; bIdx < numChannels; bIdx++) {
1325                                bandArray[bIdx] = JPSSUtilities.ATMSChannelCenterFrequencies[bIdx];
1326                                bandNames[bIdx] = "Channel " + (bIdx + 1);
1327                        }
1328                        spectTable.put(SpectrumAdapter.channelValues, bandArray);
1329                        spectTable.put(SpectrumAdapter.bandNames, bandNames);
1330
1331                        } else {
1332                                if (instrumentName.getStringValue().equals("CrIS")) {
1333                                        
1334                                        swathTable.put("XTrack", "dim1");
1335                                        swathTable.put("Track", "dim0");
1336                                        swathTable.put("geo_XTrack", "dim1");
1337                                        swathTable.put("geo_Track", "dim0");
1338                                        swathTable.put("product_name", "CrIS_SDR");
1339                                        swathTable.put(SpectrumAdapter.channelIndex_name, "dim3");
1340                                        swathTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 
1341                                         
1342                                        spectTable.put(SpectrumAdapter.channelIndex_name, "dim3");
1343                                        spectTable.put(SpectrumAdapter.FOVindex_name, "dim2");
1344                                spectTable.put(SpectrumAdapter.x_dim_name, "dim1");
1345                                spectTable.put(SpectrumAdapter.y_dim_name, "dim0");
1346                                
1347                                } else if (instrumentName.getStringValue().contains("OMPS")) {
1348                                        
1349                                        spectTable.put(SpectrumAdapter.channelIndex_name, "Channel");
1350                                swathTable.put(SpectrumAdapter.channelIndex_name, "Channel");
1351                                
1352                                swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
1353                                swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
1354                                swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
1355                                spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
1356                                spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
1357                                spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
1358                                
1359                                spectTable.put(SpectrumAdapter.channelType, "wavelength");
1360                                spectTable.put(SpectrumAdapter.channels_name, "Channel");
1361                        spectTable.put(SpectrumAdapter.x_dim_name, "XTrack");
1362                        spectTable.put(SpectrumAdapter.y_dim_name, "Track");
1363                        
1364                                int numChannels = 200;
1365                                if (instrumentName.getStringValue().equals("OMPS-TC")) {
1366                                        numChannels = 260;
1367                                }
1368                                logger.debug("Setting up OMPS adapter, num channels: " + numChannels);
1369                                float[] bandArray = new float[numChannels];
1370                                String[] bandNames = new String[numChannels];
1371                                for (int bIdx = 0; bIdx < numChannels; bIdx++) {
1372                                        bandArray[bIdx] = bIdx;
1373                                        bandNames[bIdx] = "Channel " + (bIdx + 1);
1374                                }
1375                                spectTable.put(SpectrumAdapter.channelValues, bandArray);
1376                                spectTable.put(SpectrumAdapter.bandNames, bandNames);
1377                                
1378                                } else {
1379                                        // sorry, if we can't id the instrument, we can't display the data!
1380                                        throw new VisADException("Unable to determine instrument name");
1381                                }
1382                        }
1383
1384                } else {
1385                        swathTable.put("array_dimension_names", new String[] {"Track", "XTrack"});
1386                        swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
1387                        swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
1388                }
1389                
1390                swathTable.put("scale_name", "scale_factor");
1391                swathTable.put("offset_name", "add_offset");
1392                swathTable.put("fill_value_name", "_FillValue");
1393                swathTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1));
1394                spectTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1));
1395                
1396                // set the valid range hash if data is available
1397                if (nppPP != null) {
1398                        if (nppPP.getRangeMin(pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1)) != null) {
1399                                swathTable.put("valid_range", "valid_range");
1400                        }
1401                }
1402                
1403                String unsignedAttributeStr = unsignedFlags.get(pStr);
1404                if ((unsignedAttributeStr != null) && (unsignedAttributeStr.equals("true"))) {
1405                        swathTable.put("unsigned", unsignedAttributeStr);
1406                }
1407                
1408                String unpackFlagStr = unpackFlags.get(pStr);
1409                if ((unpackFlagStr != null) && (unpackFlagStr.equals("true"))) {
1410                        swathTable.put("unpack", "true");
1411                }
1412                
1413                // For Suomi NPP data, do valid range check AFTER applying scale/offset
1414                swathTable.put("range_check_after_scaling", "true");
1415                
1416                // pass in a GranuleAggregation reader...
1417                if (! isVIIRS) {
1418                if (instrumentName.getStringValue().equals("ATMS")) {
1419                        adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
1420                        adapterCreated = true;
1421                        SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable);
1422                    DataCategory.createCategory("MultiSpectral");
1423                    categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
1424                        MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 
1425                                "BrightnessTemperature", "BrightnessTemperature", "SuomiNPP", "ATMS");
1426                        msd.setInitialWavenumber(JPSSUtilities.ATMSChannelCenterFrequencies[0]);
1427                        multiSpectralData.add(msd);
1428                } 
1429                if (instrumentName.getStringValue().equals("CrIS")) {
1430                        if (pStr.contains(crisFilter)) {
1431                                adapters[pIdx] = new CrIS_SDR_SwathAdapter(nppAggReader, swathTable);
1432                                adapterCreated = true;
1433                                CrIS_SDR_Spectrum csa = new CrIS_SDR_Spectrum(nppAggReader, spectTable);
1434                            DataCategory.createCategory("MultiSpectral");
1435                            categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
1436                                MultiSpectralData msd = new CrIS_SDR_MultiSpectralData((CrIS_SDR_SwathAdapter) adapters[pIdx], csa); 
1437                            msd.setInitialWavenumber(csa.getInitialWavenumber());
1438                            msd_CrIS.add(msd);
1439                        }
1440                }
1441                if (instrumentName.getStringValue().contains("OMPS")) {
1442                        adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
1443                        adapterCreated = true;
1444                        SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable);
1445                    DataCategory.createCategory("MultiSpectral");
1446                    categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
1447                        MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 
1448                                "RadianceEarth", "RadianceEarth", "SuomiNPP", "OMPS");
1449                        msd.setInitialWavenumber(0);
1450                        multiSpectralData.add(msd);
1451                } 
1452                if (pIdx == 0) {
1453                        // generate default subset for ATMS and OMPS
1454                        if (! instrumentName.getStringValue().equals("CrIS")) {
1455                                defaultSubset = multiSpectralData.get(pIdx).getDefaultSubset();
1456                        }
1457                }
1458                
1459                } else {
1460                        // setting NOAA-format units
1461                        String varName = pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1);
1462                        String varShortName = pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1);
1463                        String units = nppPP.getUnits(varShortName);
1464
1465                        // setting NASA-format units
1466                        if (! isNOAA) {
1467                                units = unitsNASA.get(varShortName);
1468                                // Need to set _BT variables manually, since they are created on the fly
1469                                if (varShortName.endsWith("_BT")) units = "Kelvin";
1470                        }
1471                        if (units == null) units = "Unknown";
1472                        Unit u = null;
1473                        try {
1474                                        u = Parser.parse(units);
1475                                } catch (NoSuchUnitException e) {
1476                                        u = new DerivedUnit(units);
1477                                        logger.debug("Unknown units: " + units);
1478                                } catch (ParseException e) {
1479                                        u = new DerivedUnit(units);
1480                                        logger.debug("Unparseable units: " + units);
1481                                }
1482                        // associate this variable with these units, if not done already
1483                        RealType.getRealType(varName, u);
1484                        adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
1485                        adapterCreated = true;
1486                        if (pIdx == 0) {
1487                                defaultSubset = adapters[pIdx].getDefaultSubset();
1488                        }
1489                        categories = DataCategory.parseCategories("IMAGE");
1490                }
1491                // only increment count if we created an adapter, some products are skipped
1492                if (adapterCreated) pIdx++;
1493                adapterCreated = false;
1494        }
1495
1496        if (msd_CrIS.size() > 0) {
1497                try {
1498                        MultiSpectralAggr aggr = new MultiSpectralAggr(msd_CrIS.toArray(new MultiSpectralData[msd_CrIS.size()]));
1499                        aggr.setInitialWavenumber(902.25f);
1500                        multiSpectralData.add(aggr);
1501                        defaultSubset = ((MultiSpectralData) msd_CrIS.get(0)).getDefaultSubset();
1502                } catch (Exception e) {
1503                        logger.error("Exception: ", e);
1504                }
1505        }
1506
1507        // Merge with pre-set properties
1508        Hashtable tmpHt = getProperties();
1509        tmpHt.putAll(properties);
1510        setProperties(tmpHt);
1511    }
1512
1513    public void initAfterUnpersistence() {
1514        try {
1515            String zidvPath = 
1516                    McIDASV.getStaticMcv().getStateManager().
1517                    getProperty(IdvPersistenceManager.PROP_ZIDVPATH, "");
1518            if (getTmpPaths() != null) {
1519                // New code for zipped bundles-
1520                // we want 'sources' to point to wherever the zipped data was unpacked.
1521                sources.clear();
1522                // following PersistenceManager.fixBulkDataSources, get temporary data location
1523                for (Object o : getTmpPaths()) {
1524                    String tempPath = (String) o;
1525                    // replace macro string with actual path
1526                    String expandedPath = tempPath.replace(PersistenceManager.MACRO_ZIDVPATH, zidvPath);
1527                    // we don't want to add nav files to this list!:
1528                    File f = new File(expandedPath);
1529                    if (!f.getName().matches(JPSSUtilities.SUOMI_GEO_REGEX_NOAA)) {
1530                        sources.add(expandedPath);
1531                    }
1532                }
1533
1534                // mjh fix absolute paths in filenameMap
1535                logger.debug("original filenameMap: {}", filenameMap);
1536                Iterator keyIterator = filenameMap.keySet().iterator();
1537                while (keyIterator.hasNext()) {
1538                    String keyStr = (String) keyIterator.next();
1539                    List<String> fileNames = (List<String>) filenameMap.get(keyStr);
1540                    for (int i = 0; i < fileNames.size(); i++) {
1541                        String name = fileNames.get(i);
1542                        int lastSeparator = name.lastIndexOf(File.separatorChar);
1543                        String sub = name.substring(0, lastSeparator);
1544                        name = name.replace(sub, zidvPath);
1545                        fileNames.set(i, name);
1546                    }
1547                }
1548                logger.debug("filenameMap with zidvPath: {}", filenameMap);
1549            } else {
1550                // leave in original unpersistence code - this will get run for unzipped bundles.
1551                // TODO: do we need to handle the "Save with relative paths" case specially?
1552                    if (! oldSources.isEmpty()) {
1553                    sources.clear();
1554                    for (Object o : oldSources) {
1555                        sources.add((String) o);
1556                    }
1557                }
1558            }
1559            oldSources.clear();
1560                setup();
1561        } catch (Exception e) {
1562                logger.error("Exception: ", e);
1563        }
1564    }
1565
1566    /* (non-Javadoc)
1567         * @see edu.wisc.ssec.mcidasv.data.HydraDataSource#canSaveDataToLocalDisk()
1568         */
1569        @Override
1570        public boolean canSaveDataToLocalDisk() {
1571                // At present, Suomi data is always data granules on disk
1572                return true;
1573        }
1574
1575        /* (non-Javadoc)
1576         * @see ucar.unidata.data.DataSourceImpl#saveDataToLocalDisk(java.lang.String, java.lang.Object, boolean)
1577         */
1578        @Override
1579        protected List saveDataToLocalDisk(String filePrefix, Object loadId,
1580                        boolean changeLinks) throws Exception {
1581                // need to make a list of all data granule files
1582                // PLUS all geolocation granule files, but only if accessed separate!
1583                List<String> fileList = new ArrayList<String>();
1584                for (Object o : sources) {
1585                        fileList.add((String) o);
1586                }
1587                for (String s : geoSources) {
1588                        fileList.add(s);
1589                }
1590                return fileList;
1591        }
1592
1593        public List<String> getOldSources() {
1594                return oldSources;
1595        }
1596
1597        public void setOldSources(List<String> oldSources) {
1598                this.oldSources = oldSources;
1599        }
1600    
1601    public Map<String, List<String>> getFilenameMap() {
1602        return filenameMap;
1603    }
1604
1605    public void setFilenameMap(Map<String, List<String>> filenameMap) {
1606        this.filenameMap = filenameMap;
1607    }
1608
1609        /**
1610     * Make and insert the {@link DataChoice DataChoices} for this
1611     * {@code DataSource}.
1612     */
1613    
1614    public void doMakeDataChoices() {
1615        
1616        // special loop for CrIS, ATMS, and OMPS data
1617        if (multiSpectralData.size() > 0) {
1618                for (int k = 0; k < multiSpectralData.size(); k++) {
1619                        MultiSpectralData adapter = multiSpectralData.get(k);
1620                        DataChoice choice = null;
1621                                try {
1622                                        choice = doMakeDataChoice(k, adapter);
1623                                        choice.setObjectProperty(Constants.PROP_GRANULE_COUNT, 
1624                                                        getProperty(Constants.PROP_GRANULE_COUNT, "1 Granule"));
1625                                msdMap.put(choice.getName(), adapter);
1626                                addDataChoice(choice);
1627                                } catch (Exception e) {
1628                                        logger.error("Exception: ", e);
1629                                }
1630                }
1631                return;
1632        }
1633                
1634        // all other data (VIIRS and 2D EDRs)
1635        if (adapters != null) {
1636                for (int idx = 0; idx < adapters.length; idx++) {
1637                        DataChoice choice = null;
1638                        try {
1639                                choice = doMakeDataChoice(idx, adapters[idx].getArrayName());
1640                                choice.setObjectProperty(Constants.PROP_GRANULE_COUNT, 
1641                                                        getProperty(Constants.PROP_GRANULE_COUNT, "1 Granule")); 
1642                        } 
1643                        catch (Exception e) {
1644                                e.printStackTrace();
1645                                logger.error("doMakeDataChoice failed");
1646                        }
1647
1648                        if (choice != null) {
1649                                addDataChoice(choice);
1650                        }
1651                }
1652        }
1653    }
1654
1655    private DataChoice doMakeDataChoice(int idx, String var) throws Exception {
1656        String name = var;
1657        DataSelection dataSel = new MultiDimensionSubset(defaultSubset);
1658        Hashtable subset = new Hashtable();
1659        subset.put(new MultiDimensionSubset(), dataSel);
1660        // TJJ Hack check for uber-odd case of data type varies for same variable
1661        // If it's M12 - M16, it's a BrightnessTemperature, otherwise Reflectance
1662        if (name.endsWith("BrightnessTemperatureOrReflectance")) {
1663                name = name.substring(0, name.length() - "BrightnessTemperatureOrReflectance".length());
1664                if (whichEDR.matches("M12|M13|M14|M15|M16")) {
1665                        name = name + "BrightnessTemperature";
1666                } else {
1667                        name = name + "Reflectance";
1668                }
1669        }
1670        DirectDataChoice ddc = new DirectDataChoice(this, idx, name, name, categories, subset);
1671        return ddc;
1672    }
1673    
1674    private DataChoice doMakeDataChoice(int idx, MultiSpectralData adapter) throws Exception {
1675        String name = adapter.getName();
1676        DataSelection dataSel = new MultiDimensionSubset(defaultSubset);
1677        Hashtable subset = new Hashtable();
1678        subset.put(MultiDimensionSubset.key, dataSel);
1679        subset.put(MultiSpectralDataSource.paramKey, adapter.getParameter());
1680        // TJJ Hack check for uber-odd case of data type varies for same variable
1681        // If it's M12 - M16, it's a BrightnessTemperature, otherwise Reflectance
1682        if (name.endsWith("BrightnessTemperatureOrReflectance")) {
1683                name = name.substring(0, name.length() - "BrightnessTemperatureOrReflectance".length());
1684                if (whichEDR.matches("M12|M13|M14|M15|M16")) {
1685                        name = name + "BrightnessTemperature";
1686                } else {
1687                        name = name + "Reflectance";
1688                }
1689        }
1690        DirectDataChoice ddc = new DirectDataChoice(this, new Integer(idx), name, name, categories, subset);
1691        ddc.setProperties(subset);
1692        return ddc;
1693    }
1694
1695    /**
1696     * Check to see if this {@code SuomiNPPDataSource} is equal to the object
1697     * in question.
1698     * @param o  object in question
1699     * @return true if they are the same or equivalent objects
1700     */
1701    
1702    public boolean equals(Object o) {
1703        if ( !(o instanceof SuomiNPPDataSource)) {
1704            return false;
1705        }
1706        return (this == (SuomiNPPDataSource) o);
1707    }
1708
1709    public MultiSpectralData getMultiSpectralData() {
1710        return multiSpectralData.get(0);
1711    }
1712    
1713    public MultiSpectralData getMultiSpectralData(DataChoice choice) {
1714        return msdMap.get(choice.getName());
1715    }
1716
1717    public String getDatasetName() {
1718      return filename;
1719    }
1720
1721        /**
1722         * @return the qfMap
1723         */
1724        public Map<String, QualityFlag> getQfMap() {
1725                return qfMap;
1726        }
1727
1728        public void setDatasetName(String name) {
1729      filename = name;
1730    }
1731
1732    public Map<String, double[]> getSubsetFromLonLatRect(MultiDimensionSubset select, GeoSelection geoSelection) {
1733      GeoLocationInfo ginfo = geoSelection.getBoundingBox();
1734      return adapters[0].getSubsetFromLonLatRect(select.getSubset(), ginfo.getMinLat(), ginfo.getMaxLat(),
1735                                        ginfo.getMinLon(), ginfo.getMaxLon());
1736    }
1737
1738    public synchronized Data getData(DataChoice dataChoice, DataCategory category,
1739                                DataSelection dataSelection, Hashtable requestProperties)
1740                                throws VisADException, RemoteException {
1741       return this.getDataInner(dataChoice, category, dataSelection, requestProperties);
1742    }
1743
1744
1745    protected Data getDataInner(DataChoice dataChoice, DataCategory category,
1746                                DataSelection dataSelection, Hashtable requestProperties)
1747                                throws VisADException, RemoteException {
1748
1749        //- this hack keeps the HydraImageProbe from doing a getData()
1750        //- TODO: need to use categories?
1751        if (requestProperties != null) {
1752          if ((requestProperties.toString()).equals("{prop.requester=MultiSpectral}")) {
1753            return null;
1754          }
1755        }
1756
1757        GeoLocationInfo ginfo = null;
1758        GeoSelection geoSelection = null;
1759        
1760        if ((dataSelection != null) && (dataSelection.getGeoSelection() != null)) {
1761          geoSelection = (dataSelection.getGeoSelection().getBoundingBox() != null) ? dataSelection.getGeoSelection() :
1762                                    dataChoice.getDataSelection().getGeoSelection();
1763        }
1764
1765        if (geoSelection != null) {
1766          ginfo = geoSelection.getBoundingBox();
1767        }
1768
1769        Data data = null;
1770        if (adapters == null) {
1771          return data;
1772        }
1773
1774        MultiDimensionAdapter adapter = null;
1775        
1776        // pick the adapter with the same index as the current data choice
1777        int aIdx = 0;
1778        List<DataChoice> dcl = getDataChoices();
1779        for (DataChoice dc : dcl) {
1780                if (dc.getName().equals(dataChoice.getName())) {
1781                        aIdx = dcl.indexOf(dc);
1782                        break;
1783                }
1784        }
1785
1786        adapter = adapters[aIdx];
1787
1788        try {
1789            Map<String, double[]> subset = null;
1790            if (ginfo != null) {
1791                subset = adapter.getSubsetFromLonLatRect(ginfo.getMinLat(), ginfo.getMaxLat(),
1792                                ginfo.getMinLon(), ginfo.getMaxLon(),
1793                                geoSelection.getXStride(),
1794                                geoSelection.getYStride(),
1795                                geoSelection.getZStride());
1796            }
1797            else {
1798
1799              MultiDimensionSubset select = null;
1800              Hashtable table = dataChoice.getProperties();
1801              Enumeration keys = table.keys();
1802              while (keys.hasMoreElements()) {
1803                Object key = keys.nextElement();
1804                logger.debug("Key: " + key.toString());
1805                if (key instanceof MultiDimensionSubset) {
1806                  select = (MultiDimensionSubset) table.get(key);
1807                }
1808              }  
1809              subset = select.getSubset();
1810              logger.debug("Subset size: " + subset.size());
1811
1812              if (dataSelection != null) {
1813                Hashtable props = dataSelection.getProperties();
1814                if (props != null) {
1815                  if (props.containsKey(SpectrumAdapter.channelIndex_name)) {
1816                          logger.debug("Props contains channel index key...");
1817                    double[] coords = (double[]) subset.get(SpectrumAdapter.channelIndex_name);
1818                    int idx = ((Integer) props.get(SpectrumAdapter.channelIndex_name)).intValue();
1819                    coords[0] = (double) idx;
1820                    coords[1] = (double) idx;
1821                    coords[2] = (double) 1;
1822                  }
1823                }
1824              }
1825            }
1826
1827            if (subset != null) {
1828              data = adapter.getData(subset);
1829              data = applyProperties(data, requestProperties, subset, aIdx);
1830            }
1831        } catch (Exception e) {
1832            logger.error("getData Exception: ", e);
1833        }
1834        ////////// inq1429 return FieldImpl with time dim /////////////////
1835        if (data != null) {
1836                List dateTimes = new ArrayList();
1837                dateTimes.add(new DateTime(theDate));
1838                SampledSet timeSet = (SampledSet) ucar.visad.Util.makeTimeSet(dateTimes);
1839                FunctionType ftype = new FunctionType(RealType.Time, data.getType());
1840                FieldImpl fi = new FieldImpl(ftype, timeSet);
1841                fi.setSample(0, data);
1842                data = fi;
1843        }
1844        //////////////////////////////////////////////////////////////////
1845        return data;
1846    }
1847
1848    protected Data applyProperties(Data data, Hashtable requestProperties, Map<String, double[]> subset, int adapterIndex)
1849          throws VisADException, RemoteException {
1850      Data new_data = data;
1851
1852      if (requestProperties == null) {
1853        new_data = data;
1854        return new_data;
1855      }
1856
1857      return new_data;
1858    }
1859
1860    protected void initDataSelectionComponents(
1861         List<DataSelectionComponent> components,
1862             final DataChoice dataChoice) {
1863      
1864                  try {
1865                          // inq1429: need to handle FieldImpl here
1866                          FieldImpl thing = (FieldImpl) dataChoice.getData(null);
1867                          FlatField image;
1868                          if (GridUtil.isTimeSequence(thing)) {
1869                                  image = (FlatField) thing.getSample(0);
1870                          } else {
1871                                  image = (FlatField) thing;
1872                          }
1873                          if (image != null) {
1874                                  PreviewSelection ps = new PreviewSelection(dataChoice, image, null);
1875                                  // Region subsetting not yet implemented for CrIS data
1876                                  if (instrumentName.getStringValue().equals("CrIS")) {
1877                                          ps.enableSubsetting(false);
1878                                  }
1879                                  components.add(ps);
1880                          }
1881                  } catch (Exception e) {
1882                          logger.error("Can't make PreviewSelection: ", e);
1883                  }
1884      
1885    }
1886    
1887    /**
1888     * Add {@code Integer->String} translations to IDV's
1889     * {@literal "translations"} resource, so they will be made available to
1890     * the data probe of Image Display's.
1891     */
1892    public void initQfTranslations() {
1893        
1894        Map<String, Map<Integer, String>> translations =
1895                getIdv().getResourceManager().
1896                getTranslationsHashtable();
1897        
1898        for (String qfKey : qfMap.keySet()) {
1899                // This string needs to match up with the data choice name:
1900                String qfKeySubstr = qfKey.replace("All_Data/", "");
1901                // check if we've already added map for this QF
1902                if (!translations.containsKey(qfKeySubstr)) {
1903                        Map<String, String> hm = qfMap.get(qfKey).getHm();
1904                        Map<Integer, String> newMap = 
1905                                        new HashMap<Integer, String>(hm.size());
1906                        for (String dataValueKey : hm.keySet()) {
1907                                // convert Map<String, String> to Map<Integer, String>
1908                                Integer intKey = Integer.parseInt(dataValueKey);
1909                                newMap.put(intKey, hm.get(dataValueKey));
1910                        }
1911                        translations.put(qfKeySubstr, newMap);
1912                }
1913        }
1914    }
1915    
1916}