001    /*
002     * This file is part of McIDAS-V
003     *
004     * Copyright 2007-2013
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    
029    package edu.wisc.ssec.mcidasv.data.hydra;
030    
031    import edu.wisc.ssec.mcidasv.McIDASV;
032    import edu.wisc.ssec.mcidasv.PersistenceManager;
033    import edu.wisc.ssec.mcidasv.data.HydraDataSource;
034    import edu.wisc.ssec.mcidasv.data.PreviewSelection;
035    import edu.wisc.ssec.mcidasv.data.QualityFlag;
036    
037    import java.io.ByteArrayInputStream;
038    import java.io.File;
039    import java.io.FilenameFilter;
040    
041    import java.rmi.RemoteException;
042    
043    import java.text.SimpleDateFormat;
044    
045    import java.util.ArrayList;
046    import java.util.Date;
047    import java.util.Enumeration;
048    import java.util.HashMap;
049    import java.util.Hashtable;
050    import java.util.Iterator;
051    import java.util.LinkedHashMap;
052    import java.util.LinkedHashSet;
053    import java.util.List;
054    import java.util.SimpleTimeZone;
055    import java.util.StringTokenizer;
056    
057    import org.jdom2.Document;
058    import org.jdom2.Element;
059    import org.jdom2.Namespace;
060    import org.jdom2.output.XMLOutputter;
061    
062    import org.slf4j.Logger;
063    import org.slf4j.LoggerFactory;
064    
065    import ucar.ma2.ArrayFloat;
066    import ucar.ma2.DataType;
067    
068    import ucar.nc2.Attribute;
069    import ucar.nc2.Dimension;
070    import ucar.nc2.Group;
071    import ucar.nc2.NetcdfFile;
072    import ucar.nc2.Variable;
073    import ucar.nc2.dataset.VariableDS;
074    
075    import ucar.unidata.data.DataCategory;
076    import ucar.unidata.data.DataChoice;
077    import ucar.unidata.data.DataSelection;
078    import ucar.unidata.data.DataSelectionComponent;
079    import ucar.unidata.data.DataSourceDescriptor;
080    import ucar.unidata.data.DirectDataChoice;
081    import ucar.unidata.data.GeoLocationInfo;
082    import ucar.unidata.data.GeoSelection;
083    import ucar.unidata.idv.IdvPersistenceManager;
084    
085    import ucar.unidata.util.Misc;
086    
087    import visad.Data;
088    import visad.FlatField;
089    import visad.VisADException;
090    
091    import visad.util.Util;
092    
093    /**
094     * A data source for NPOESS Preparatory Project (Suomi NPP) data
095     * This will probably move, but we are placing it here for now
096     * since we are leveraging some existing code used for HYDRA.
097     */
098    
099    public class SuomiNPPDataSource extends HydraDataSource {
100    
101            private static final Logger logger = LoggerFactory.getLogger(SuomiNPPDataSource.class);
102            
103            /** Sources file */
104        protected String filename;
105        
106        // for loading bundles, store granule lists and geo lists here
107        protected List<String> oldSources = new ArrayList<String>();
108        protected List<String> geoSources = new ArrayList<String>();
109    
110        protected MultiDimensionReader nppAggReader;
111    
112        protected MultiDimensionAdapter[] adapters = null;
113        
114        private ArrayList<MultiSpectralData> msd_CrIS = new ArrayList<MultiSpectralData>();
115        private ArrayList<MultiSpectralData> multiSpectralData = new ArrayList<MultiSpectralData>();
116        private HashMap<String, MultiSpectralData> msdMap = new HashMap<String, MultiSpectralData>();
117        private HashMap<String, QualityFlag> qfMap = new HashMap<String, QualityFlag>();
118    
119        private static final String DATA_DESCRIPTION = "Suomi NPP Data";
120        
121        // instrument related variables and flags
122        Attribute instrumentName = null;
123        private String productName = null;
124        
125        // product related variables and flags
126        boolean isEDR = false;
127        
128        // for now, we are only handling CrIS variables that match this filter and SCAN dimensions
129        private String crisFilter = "ES_Real";
130        
131        // for now, we are only handling OMPS variables that match this filter and SCAN dimensions
132        private String ompsFilter = "Radiance";
133    
134        private HashMap defaultSubset;
135        public TrackAdapter track_adapter;
136    
137        private List categories;
138        private boolean hasChannelSelect = false;
139        private boolean hasImagePreview = true;
140        private boolean isCombinedProduct = false;
141        private boolean nameHasBeenSet = false;
142    
143            private FlatField previewImage = null;
144        
145        // need our own separator char since it's always Unix-style in the Suomi NPP files
146        private static final String SEPARATOR_CHAR = "/";
147        
148        // date formatter for converting Suomi NPP day/time to something we can use
149        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss.SSS");
150        
151        // date formatter for how we want to show granule day/time on display
152        SimpleDateFormat sdfOut = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss z");
153    
154        /**
155         * Zero-argument constructor for construction via unpersistence.
156         */
157        
158        public SuomiNPPDataSource() {
159        }
160        
161        public SuomiNPPDataSource(String fileName) throws VisADException {
162            this(null, Misc.newList(fileName), null);
163            logger.warn("filename only constructor call..");
164        }
165    
166        /**
167         * Construct a new Suomi NPP HDF5 data source.
168         * @param  descriptor  descriptor for this <code>DataSource</code>
169         * @param  fileName  name of the hdf file to read
170         * @param  properties  hashtable of properties
171         *
172         * @throws VisADException problem creating data
173         */
174        
175        public SuomiNPPDataSource(DataSourceDescriptor descriptor,
176                                     String fileName, Hashtable properties)
177                throws VisADException {
178            this(descriptor, Misc.newList(fileName), properties);
179            logger.debug("SuomiNPPDataSource called, single file selected: " + fileName);
180        }
181    
182        /**
183         * Construct a new Suomi NPP HDF5 data source.
184         * @param  descriptor  descriptor for this <code>DataSource</code>
185         * @param  sources   List of filenames
186         * @param  properties  hashtable of properties
187         *
188         * @throws VisADException problem creating data
189         */
190        
191        public SuomiNPPDataSource(DataSourceDescriptor descriptor,
192                                     List<String> newSources, Hashtable properties)
193                throws VisADException {
194            super(descriptor, newSources, DATA_DESCRIPTION, properties);
195            logger.debug("SuomiNPPDataSource constructor called, file count: " + sources.size());
196    
197            filename = (String) sources.get(0);
198            setDescription("Suomi NPP");
199            
200            for (Object o : sources) {
201                    logger.debug("Suomi NPP source file: " + (String) o);
202            }
203    
204            setup();
205        }
206    
207        public void setup() throws VisADException {
208    
209            // store filenames for possible bundle unpersistence
210            for (Object o : sources) {
211                    oldSources.add((String) o);
212            }
213            
214            // time zone for product labels
215            SimpleTimeZone stz = new SimpleTimeZone(0, "GMT");
216            sdf.setTimeZone(stz);
217            sdfOut.setTimeZone(stz);
218            
219            // looking to populate 3 things - path to lat, path to lon, path to relevant products
220            String pathToLat = null;
221            String pathToLon = null;
222            LinkedHashSet<String> pathToProducts = new LinkedHashSet<String>();
223            
224            // flag to indicate data is 3-dimensions (X, Y, channel or band)
225            boolean is3D = false;
226            
227            // check source filenames to see if this is a combined product. everything
228            // from last file separator to first underscore should be product info
229            int lastSeparator = filename.lastIndexOf(File.separatorChar);
230            int firstUnderscore = filename.indexOf("_", lastSeparator + 1);
231            String prodStr = filename.substring(lastSeparator + 1, firstUnderscore);
232            StringTokenizer st = new StringTokenizer(prodStr, "-");
233            logger.debug("SNPPDS check for embedded GEO, tokenizing: " + prodStr);
234            while (st.hasMoreTokens()) {
235                    String singleProd = st.nextToken();
236                    logger.debug("Next token: " + singleProd);
237                    for (int i = 0; i < JPSSUtilities.geoProductIDs.length; i++) {
238                            if (singleProd.equals(JPSSUtilities.geoProductIDs[i])) {
239                                    logger.debug("Setting isCombinedProduct true, Found embedded GEO: " + singleProd);
240                                    isCombinedProduct = true;
241                                    break;
242                            }
243                    }
244            }
245            
246            // various metatdata we'll need to gather on a per-product basis
247            LinkedHashMap<String, String> unsignedFlags = new LinkedHashMap<String, String>();
248            LinkedHashMap<String, String> unpackFlags = new LinkedHashMap<String, String>();
249            
250            // geo product IDs for each granule
251            LinkedHashSet<String> geoProductIDs = new LinkedHashSet<String>();
252            
253            // aggregations will use sets of NetCDFFile readers
254            ArrayList<NetCDFFile> ncdfal = new ArrayList<NetCDFFile>();
255            
256            // we should be able to find an XML Product Profile for each data/product type
257            SuomiNPPProductProfile nppPP = null;
258                    
259            try {
260                    
261                    nppPP = new SuomiNPPProductProfile();
262                    
263                    // for each source file provided get the nominal time
264                    for (int fileCount = 0; fileCount < sources.size(); fileCount++) {
265                            // need to open the main NetCDF file to determine the geolocation product
266                            NetcdfFile ncfile = null;
267                            String fileAbsPath = null;
268                            try {
269                                    fileAbsPath = (String) sources.get(fileCount);
270                                    logger.debug("Trying to open file: " + fileAbsPath);
271                                    ncfile = NetcdfFile.open(fileAbsPath);
272                                    if (! isCombinedProduct) {
273                                                    Attribute a = ncfile.findGlobalAttribute("N_GEO_Ref");
274                                                    logger.debug("Value of GEO global attribute: "
275                                                                    + a.getStringValue());
276                                                    String tmpGeoProductID = a.getStringValue();
277                                                    geoProductIDs.add(tmpGeoProductID);
278                                            }
279                                            Group rg = ncfile.getRootGroup();
280    
281                            List<Group> gl = rg.getGroups();
282                            if (gl != null) {
283                                    for (Group g : gl) {
284                                            logger.debug("Group name: " + g.getFullName());
285                                            // when we find the Data_Products group, go down another group level and pull out 
286                                            // what we will use for nominal day and time (for now anyway).
287                                            // XXX TJJ fileCount check is so we don't count the GEO file in time array!
288                                            if (g.getFullName().contains("Data_Products") && (fileCount != sources.size())) {
289                                                    boolean foundDateTime = false;
290                                                    List<Group> dpg = g.getGroups();
291                                                    
292                                                    // cycle through once looking for XML Product Profiles
293                                                    for (Group subG : dpg) {
294                                                            
295                                                            String subName = subG.getFullName();
296                                                            // use actual product, not geolocation, to id XML Product Profile
297                                                            if (! subName.contains("-GEO")) {
298                                                                    // determine the instrument name (VIIRS, ATMS, CrIS, OMPS)
299                                                                    instrumentName = subG.findAttribute("Instrument_Short_Name");
300    
301                                                                    // note any EDR products, will need to check for and remove
302                                                                    // fill scans later
303                                                                    Attribute adtt = subG.findAttribute("N_Dataset_Type_Tag");
304                                                                    if (adtt != null) {
305                                                                            String baseName = adtt.getStringValue();
306                                                                            if ((baseName != null) && (baseName.equals("EDR"))) {
307                                                                                    isEDR = true;
308                                                                            }
309                                                                    }
310                                                                    
311                                                                    // This is also where we find the attribute which tells us which
312                                                                    // XML Product Profile to use!
313                                                                    Attribute axpp = subG.findAttribute("N_Collection_Short_Name");
314                                                                    if (axpp != null) {
315                                                                            String baseName = axpp.getStringValue();
316                                                                            productName = baseName;
317                                                                            String productProfileFileName = nppPP.getProfileFileName(baseName);
318                                                                            logger.debug("Found profile: " + productProfileFileName);
319                                                                            if (productProfileFileName == null) {
320                                                                                    throw new Exception("XML Product Profile not found in catalog");
321                                                                            }
322                                                                            try {
323                                                                                    nppPP.addMetaDataFromFile(productProfileFileName);
324                                                                            } catch (Exception nppppe) {
325                                                                                    logger.error("Error parsing XML Product Profile: " + productProfileFileName);
326                                                                                    throw new Exception("XML Product Profile Error");
327                                                                            }
328                                                                    }
329                                                            }
330                                                    }
331                                                    
332                                                    // 2nd pass through sub-group to extract date/time for aggregation
333                                                    for (Group subG : dpg) {
334                                                            List<Variable> vl = subG.getVariables();
335                                                            for (Variable v : vl) {
336                                                                    Attribute aDate = v.findAttribute("AggregateBeginningDate");
337                                                                    Attribute aTime = v.findAttribute("AggregateBeginningTime");
338                                                                    // did we find the attributes we are looking for?
339                                                                    if ((aDate != null) && (aTime != null)) {
340                                                                            String sDate = aDate.getStringValue();
341                                                                            String sTime = aTime.getStringValue();
342                                                                            logger.debug("For day/time, using: " + sDate + sTime.substring(0, sTime.indexOf('Z') - 3));
343                                                                            Date d = sdf.parse(sDate + sTime.substring(0, sTime.indexOf('Z') - 3));
344                                                                            foundDateTime = true;
345                                                                            // set time for display to day/time of 1st granule examined
346                                                                            if (! nameHasBeenSet) {
347                                                                                    setName(instrumentName.getStringValue() + " " + sdfOut.format(d)
348                                                                                                    + ", " + sources.size() + " Granule");
349                                                                                    nameHasBeenSet = true;
350                                                                            }
351                                                                            break;
352                                                                    }
353                                                            }
354                                                            if (foundDateTime) break;
355                                                    }
356                                                    if (! foundDateTime) {
357                                                            throw new VisADException("No date time found in Suomi NPP granule");
358                                                    }
359                                            }                                       
360                                    }
361                            }
362                            } catch (Exception e) {
363                                    logger.debug("Exception during processing of file: " + fileAbsPath);
364                                    throw (e);
365                            } finally {
366                                    ncfile.close();
367                            }
368                    }
369                    
370                    // build each union aggregation element
371                    Iterator<String> iterator = geoProductIDs.iterator();
372                    for (int elementNum = 0; elementNum < sources.size(); elementNum++) {
373                            
374                            String s = (String) sources.get(elementNum);
375                            
376                            // build an XML (NCML actually) representation of the union aggregation of these two files
377                            Namespace ns = Namespace.getNamespace("http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2");
378                            Element root = new Element("netcdf", ns);
379                            Document document = new Document(root);
380    
381                            Element agg = new Element("aggregation", ns);
382                            agg.setAttribute("type", "union");
383                            
384                            Element fData = new Element("netcdf", ns);
385                            fData.setAttribute("location", s);
386                            agg.addContent(fData);
387                            
388                            if (! isCombinedProduct) {
389                                    Element fGeo  = new Element("netcdf", ns);
390            
391                                    String geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1);
392                                    // check if we have the whole file name or just the prefix
393                                    String geoProductID = iterator.next();
394                                    if (geoProductID.endsWith("h5")) {
395                                            geoFilename += geoProductID;
396                                    } else {
397                                            geoFilename += geoProductID;
398                                            geoFilename += s.substring(s.lastIndexOf(File.separatorChar) + 6);
399                                    }
400                                    // Be sure file as specified by N_GEO_Ref global attribute really is there.
401                                    File tmpGeo = new File(geoFilename);
402                                    if (! tmpGeo.exists()) {
403                                            // Ok, the expected file defined (supposedly) exactly by a global att is not there...
404                                            // We need to check for similar geo files with different creation dates
405                                            String geoFileRelative = geoFilename.substring(geoFilename.lastIndexOf(File.separatorChar) + 1);
406                                            // also check for Terrain Corrected version of geo
407                                            String geoTerrainCorrected = geoFileRelative;
408                                            geoTerrainCorrected = geoTerrainCorrected.replace("OD", "TC");
409                                            geoTerrainCorrected = geoTerrainCorrected.replace("MG", "TC");
410                                            
411                                            // now we make a file filter, and see if a matching geo file is present
412                                            File fList = new File(geoFilename.substring(0, geoFilename.lastIndexOf(File.separatorChar) + 1)); // current directory
413    
414                                            FilenameFilter geoFilter = new FilenameFilter() {
415                                                    public boolean accept(File dir, String name) {
416                                                            if (name.matches(JPSSUtilities.SUOMI_GEO_REGEX)) {
417                                                                    return true;
418                                                            } else {
419                                                                    return false;
420                                                            }
421                                                    }
422                                            };
423                                            
424                                            File[] files = fList.listFiles(geoFilter);
425                                            for (File file : files) {
426                                                    if (file.isDirectory()) {
427                                                            continue;
428                                                    }
429                                                    // get the file name for convenience
430                                                    String fName = file.getName();
431                                                    // is it one of the standard Ellipsoid geo types we are looking for?
432                                                    if (fName.substring(0, 5).equals(geoFileRelative.substring(0, 5))) {
433                                                            int geoStartIdx = geoFileRelative.indexOf("_d");
434                                                            int prdStartIdx = fName.indexOf("_d");
435                                                            String s1 = geoFileRelative.substring(geoStartIdx, geoStartIdx + 35);
436                                                            String s2 = fName.substring(prdStartIdx, prdStartIdx + 35);
437                                                            if (s1.equals(s2)) {
438                                                                    geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1) + fName;
439                                                                    break;
440                                                            }
441                                                    }
442                                                    // same check, but for Terrain Corrected version
443                                                    if (fName.substring(0, 5).equals(geoTerrainCorrected.substring(0, 5))) {
444                                                            int geoStartIdx = geoTerrainCorrected.indexOf("_d");
445                                                            int prdStartIdx = fName.indexOf("_d");
446                                                            String s1 = geoTerrainCorrected.substring(geoStartIdx, geoStartIdx + 35);
447                                                            String s2 = fName.substring(prdStartIdx, prdStartIdx + 35);
448                                                            if (s1.equals(s2)) {
449                                                                    geoFilename = s.substring(0, s.lastIndexOf(File.separatorChar) + 1) + fName;
450                                                                    break;
451                                                            }
452                                                    }
453                                            }
454                                    }
455                                    logger.debug("Cobbled together GEO file name: " + geoFilename);
456                                    fGeo.setAttribute("location", geoFilename);
457                                    // add this to list used if we create a zipped bundle
458                                    geoSources.add(geoFilename);
459                                    agg.addContent(fGeo);
460                            }
461    
462                            root.addContent(agg);    
463                        XMLOutputter xmlOut = new XMLOutputter();
464                        String ncmlStr = xmlOut.outputString(document);
465                        ByteArrayInputStream is = new ByteArrayInputStream(ncmlStr.getBytes());                     
466                        MultiDimensionReader netCDFReader = new NetCDFFile(is);
467                        
468                    // let's try and look through the NetCDF reader and see what we can learn...
469                    NetcdfFile ncdff = ((NetCDFFile) netCDFReader).getNetCDFFile();
470                    
471                    Group rg = ncdff.getRootGroup();
472                    // this is a list filled with unpacked qflag products, if any
473                    ArrayList<VariableDS> qfProds = new ArrayList<VariableDS>();
474    
475                    List<Group> gl = rg.getGroups();
476                    if (gl != null) {
477                            for (Group g : gl) {
478                                    logger.debug("Group name: " + g.getFullName());
479                                    // XXX just temporary - we are looking through All_Data, finding displayable data
480                                    if (g.getFullName().contains("All_Data")) {
481                                            List<Group> adg = g.getGroups();
482                                            int xDim = -1;
483                                            int yDim = -1;
484    
485                                            // two sub-iterations, first one to find geolocation and product dimensions
486                                            for (Group subG : adg) {
487                                                    logger.debug("Sub group name: " + subG.getFullName());
488                                                    String subName = subG.getFullName();
489                                                    if (subName.contains("-GEO")) {
490                                                            // this is the geolocation data
491                                                            List<Variable> vl = subG.getVariables();
492                                                            for (Variable v : vl) {
493                                                                    if (v.getFullName().endsWith(SEPARATOR_CHAR + "Latitude")) {
494                                                                            pathToLat = v.getFullName();
495                                                                            logger.debug("Lat/Lon Variable: " + v.getFullName());
496                                                                            // get the dimensions of the lat variable
497                                                                            Dimension dAlongTrack = v.getDimension(0);
498                                                                            yDim = dAlongTrack.getLength();
499                                                                            logger.debug("Lat along track dim: " + dAlongTrack.getLength());
500                                                                            Dimension dAcrossTrack = v.getDimension(1);
501                                                                            xDim = dAcrossTrack.getLength();
502                                                                            logger.debug("Lat across track dim: " + dAcrossTrack.getLength());
503                                                                    }
504                                                                    if (v.getFullName().endsWith(SEPARATOR_CHAR + "Longitude")) {
505                                                                            // we got dimensions from lat, don't need 'em twice, but need path
506                                                                            pathToLon = v.getFullName();
507                                                                    }
508                                                            } 
509                                                    }
510                                            }
511    
512                                            // second to identify displayable products
513                                            for (Group subG : adg) {
514                                                    logger.debug("Sub group name: " + subG.getFullName());
515                                                    // this is the product data
516                                                    List<Variable> vl = subG.getVariables();
517                                                    for (Variable v : vl) {
518                                                            boolean useThis = false;
519                                                            String vName = v.getFullName();
520                                                            logger.debug("Variable: " + vName);
521                                                            String varShortName = vName.substring(vName.lastIndexOf(SEPARATOR_CHAR) + 1);
522    
523                                                            // Special code to handle quality flags. We throw out anything
524                                                            // that does not match bounds of the geolocation data
525                                                            
526                                                            if (varShortName.startsWith("QF")) {
527                                                                    
528                                                                    logger.debug("Handling Quality Flag: " + varShortName);
529                                                                    
530                                                                    // this check is done later for ALL varialbles, but we need
531                                                                    // it early here to weed out those quality flags that are 
532                                                                    // simply a small set of data w/no granule geo nbounds
533                                                                    boolean xScanOk = false;
534                                                                    boolean yScanOk = false;
535                                                                    List<Dimension> dl = v.getDimensions();
536                                                                    
537                                                                    // toss out > 2D Quality Flags 
538                                                                    if (dl.size() > 2) {
539                                                                            logger.debug("SKIPPING QF, > 2D: " + varShortName);
540                                                                            continue;
541                                                                    }
542                                                                    
543                                                                    for (Dimension d : dl) {
544                                                                            // in order to consider this a displayable product, make sure
545                                                                            // both scan direction dimensions are present and look like a granule
546                                                                            if (d.getLength() == xDim) {
547                                                                                    xScanOk = true;
548                                                                            }
549                                                                            if (d.getLength() == yDim) {
550                                                                                    yScanOk = true;
551                                                                            }
552                                                                    }
553                                                                    
554                                                                    if (! (xScanOk && yScanOk)) {
555                                                                            logger.debug("SKIPPING QF, does not match geo bounds: " + varShortName);
556                                                                            continue;
557                                                                    }
558                                                                    
559                                                                    ArrayList<QualityFlag> qfal = nppPP.getQualityFlags(varShortName);
560                                                                    if (qfal != null) {
561                                                                            for (QualityFlag qf : qfal) {
562                                                                                    qf.setPackedName(vName);
563                                                                                    // make a copy of the qflag variable
564                                                                                    // NOTE: by using a VariableDS here, the original
565                                                                                    // variable is used for the I/O, this matters!
566                                                                                    VariableDS vqf = new VariableDS(subG, v, false);
567                                                                                    // prefix with QF num to help guarantee uniqueness across groups
568                                                                                    // this will cover most cases, but could still be dupe names
569                                                                                    // within a single QF.  This is handled when fetching XMLPP metadata
570                                                                                    vqf.setShortName(
571                                                                                                    varShortName.substring(0, 3) + "_" + qf.getName()
572                                                                                    );
573                                                                                    logger.debug("New var full name: " + vqf.getFullName());
574                                                                            qfProds.add(vqf);
575                                                                            qfMap.put(vqf.getFullName(), qf);
576                                                                            }
577                                                                    }
578                                                            }
579    
580                                                            // for CrIS instrument, only taking real calibrated values for now
581                                                            if (instrumentName.getStringValue().equals("CrIS")) {
582                                                                    if (! varShortName.startsWith(crisFilter)) {
583                                                                            logger.debug("Skipping variable: " + varShortName);
584                                                                            continue;
585                                                                    }
586                                                            }
587    
588                                                            // for OMPS, only Radiance for now...
589                                                            if (instrumentName.getStringValue().contains("OMPS")) {
590                                                                    if (! varShortName.startsWith(ompsFilter)) {
591                                                                            logger.debug("Skipping variable: " + varShortName);
592                                                                            continue;
593                                                                    }
594                                                            }
595    
596                                                            DataType dt = v.getDataType();
597                                                            if ((dt.getSize() != 4) && (dt.getSize() != 2) && (dt.getSize() != 1)) {
598                                                                    logger.debug("Skipping data of size: " + dt.getSize());
599                                                                    continue;
600                                                            }
601    
602                                                            List<Dimension> dl = v.getDimensions();
603                                                            if (dl.size() > 4) {
604                                                                    logger.debug("Skipping data of dimension: " + dl.size());
605                                                                    continue;
606                                                            }
607    
608                                                            // for now, skip any 3D VIIRS data
609                                                            if (instrumentName.getStringValue().equals("VIIRS")) {
610                                                                    if (dl.size() == 3) {
611                                                                            logger.debug("Skipping VIIRS 3D data for now...");
612                                                                            continue;
613                                                                    }
614                                                            }
615    
616                                                            boolean xScanOk = false;
617                                                            boolean yScanOk = false;
618                                                            for (Dimension d : dl) {
619                                                                    // in order to consider this a displayable product, make sure
620                                                                    // both scan direction dimensions are present and look like a granule
621                                                                    if (d.getLength() == xDim) {
622                                                                            xScanOk = true;
623                                                                    }
624                                                                    if (d.getLength() == yDim) {
625                                                                            yScanOk = true;
626                                                                    }
627                                                            }
628    
629                                                            if (xScanOk && yScanOk) {
630                                                                    logger.debug("Will probably use this variable, a few more checks...");
631                                                                    useThis = true;
632                                                            }
633    
634                                                            // new way to validate ATMS variables
635                                                            if (instrumentName.getStringValue().equals("ATMS")) {
636                                                                    boolean isDisplayableATMS = false;
637                                                                    // check variable dimensions, if num channels not present, ditch it
638                                                                    for (Dimension d : dl) {
639                                                                            if (d.getLength() == JPSSUtilities.ATMSChannelCenterFrequencies.length) {
640                                                                                    isDisplayableATMS = true;
641                                                                                    logger.debug("This variable appears to be displayable ATMS");
642                                                                                    break;
643                                                                            }
644                                                                    }
645                                                                    if (! isDisplayableATMS) {
646                                                                            useThis = false;
647                                                                    }
648                                                            }
649    
650                                                            // sensor data with a channel dimension
651                                                            if (useThis) {
652                                                                    if ((instrumentName.getStringValue().equals("CrIS")) ||
653                                                                                    (instrumentName.getStringValue().equals("ATMS")) || 
654                                                                                    (instrumentName.getStringValue().contains("OMPS"))) {
655                                                                            is3D = true;
656                                                                            hasChannelSelect = true;
657                                                                            logger.debug("Handling 3-D data source...");
658                                                                    }
659                                                            }
660    
661                                                            if (useThis) { 
662                                                                    // loop through the variable list again, looking for a corresponding "Factors"
663                                                                    float scaleVal = 1f;
664                                                                    float offsetVal = 0f;
665                                                                    boolean unpackFlag = false;
666    
667                                                                    //   if the granule has an entry for this variable name
668                                                                    //     get the data, data1 = scale, data2 = offset
669                                                                    //     create and poke attributes with this data
670                                                                    //   endif
671    
672                                                                    String factorsVarName = nppPP.getScaleFactorName(varShortName);
673                                                                    logger.debug("Mapping: " + varShortName + " to: " + factorsVarName);
674                                                                    if (factorsVarName != null) {
675                                                                            for (Variable fV : vl) {
676                                                                                    if (fV.getShortName().equals(factorsVarName)) {
677                                                                                            logger.debug("Pulling scale and offset values from variable: " + fV.getShortName());
678                                                                                            ucar.ma2.Array a = fV.read();
679                                                                                            float[] so = (float[]) a.copyTo1DJavaArray();
680                                                                                            scaleVal = so[0];
681                                                                                            logger.debug("Scale value: " + scaleVal);
682                                                                                            offsetVal = so[1];
683                                                                                            logger.debug("Offset value: " + offsetVal);
684                                                                                            unpackFlag = true;
685                                                                                            break;
686                                                                                    }
687                                                                            }
688                                                                    }
689    
690                                                                    // poke in scale/offset attributes for now
691    
692                                                                    Attribute a1 = new Attribute("scale_factor", scaleVal);
693                                                                    v.addAttribute(a1);
694                                                                    Attribute a2 = new Attribute("add_offset", offsetVal);
695                                                                    v.addAttribute(a2);  
696    
697                                                                    // add valid range and fill value attributes here
698                                                                    // try to fill in valid range
699                                                                    if (nppPP.hasNameAndMetaData(varShortName)) {
700                                                                            String rangeMin = nppPP.getRangeMin(varShortName);
701                                                                            String rangeMax = nppPP.getRangeMax(varShortName);
702                                                                            logger.debug("range min: " + rangeMin);
703                                                                            logger.debug("range max: " + rangeMax);
704                                                                            // only store range attribute if VALID range found
705                                                                            if ((rangeMin != null) && (rangeMax != null)) {
706                                                                                    int [] shapeArr = new int [] { 2 };
707                                                                                    ArrayFloat af = new ArrayFloat(shapeArr);
708                                                                                    try {
709                                                                                            af.setFloat(0, Float.parseFloat(rangeMin));
710                                                                                    } catch (NumberFormatException nfe) {
711                                                                                            af.setFloat(0, new Float(Integer.MIN_VALUE));
712                                                                                    }
713                                                                                    try {
714                                                                                            af.setFloat(1, Float.parseFloat(rangeMax));
715                                                                                    } catch (NumberFormatException nfe) {
716                                                                                            af.setFloat(1, new Float(Integer.MAX_VALUE));
717                                                                                    }
718                                                                                    Attribute rangeAtt = new Attribute("valid_range", af);
719                                                                                    v.addAttribute(rangeAtt);
720                                                                            }
721    
722                                                                            // check for and load fill values too...
723    
724                                                                            // we need to check two places, first, the XML product profile
725                                                                            ArrayList<Float> fval = nppPP.getFillValues(varShortName);
726    
727                                                                            // 2nd, does the variable already have one defined?
728                                                                            // if there was already a fill value associated with this variable, make
729                                                                            // sure we bring that along for the ride too...
730                                                                            Attribute aFill = v.findAttribute("_FillValue");
731    
732                                                                            // determine size of our fill value array
733                                                                            int fvArraySize = 0;
734                                                                            if (aFill != null) fvArraySize++;
735                                                                            if (! fval.isEmpty()) fvArraySize += fval.size();
736                                                                            int [] fillShape = new int [] { fvArraySize };
737    
738                                                                            // allocate the array
739                                                                            ArrayFloat afFill = new ArrayFloat(fillShape);
740    
741                                                                            // and FINALLY, fill it!
742                                                                            if (! fval.isEmpty()) {
743                                                                                    for (int fillIdx = 0; fillIdx < fval.size(); fillIdx++) {
744                                                                                            afFill.setFloat(fillIdx, fval.get(fillIdx));
745                                                                                            logger.debug("Adding fill value (from XML): " + fval.get(fillIdx));
746                                                                                    }
747                                                                            }
748    
749                                                                            if (aFill != null) {
750                                                                                    Number n = aFill.getNumericValue();
751                                                                                    // is the data unsigned?
752                                                                                    Attribute aUnsigned = v.findAttribute("_Unsigned");
753                                                                                    float fillValAsFloat = Float.NaN;
754                                                                                    if (aUnsigned != null) {
755                                                                                            if (aUnsigned.getStringValue().equals("true")) {
756                                                                                                    DataType fvdt = aFill.getDataType();
757                                                                                                    logger.debug("Data String: " + aFill.toString());
758                                                                                                    logger.debug("DataType primitive type: " + fvdt.getPrimitiveClassType());
759                                                                                                    // signed byte that needs conversion?
760                                                                                                    if (fvdt.getPrimitiveClassType() == byte.class) {
761                                                                                                            fillValAsFloat = (float) Util.unsignedByteToInt(n.byteValue());
762                                                                                                    }
763                                                                                                    else if (fvdt.getPrimitiveClassType() == short.class) {
764                                                                                                            fillValAsFloat = (float) Util.unsignedShortToInt(n.shortValue());
765                                                                                                    } else {
766                                                                                                            fillValAsFloat = n.floatValue();
767                                                                                                    }
768                                                                                            }
769                                                                                    }
770                                                                                    afFill.setFloat(fvArraySize - 1, fillValAsFloat);
771                                                                                    logger.debug("Adding fill value (from variable): " + fillValAsFloat);
772                                                                            }
773                                                                            Attribute fillAtt = new Attribute("_FillValue", afFill);
774                                                                            v.addAttribute(fillAtt);
775                                                                    }
776    
777                                                                    Attribute aUnsigned = v.findAttribute("_Unsigned");
778                                                                    if (aUnsigned != null) {
779                                                                            logger.debug("_Unsigned attribute value: " + aUnsigned.getStringValue());
780                                                                            unsignedFlags.put(v.getFullName(), aUnsigned.getStringValue());
781                                                                    } else {
782                                                                            unsignedFlags.put(v.getFullName(), "false");
783                                                                    }
784    
785                                                                    if (unpackFlag) {
786                                                                            unpackFlags.put(v.getFullName(), "true");
787                                                                    } else {
788                                                                            unpackFlags.put(v.getFullName(), "false");
789                                                                    }
790    
791                                                                    logger.debug("Adding product: " + v.getFullName());
792                                                                    pathToProducts.add(v.getFullName());
793    
794                                                            }
795                                                    }
796                                            }
797                                    }
798                            }
799                    }
800                    
801                    // add in any unpacked qflag products
802                    for (VariableDS qfV: qfProds) {
803                            // skip the spares - they are reserved for future use
804                            if (qfV.getFullName().endsWith("Spare")) {
805                                    continue;
806                            }
807                            // String.endwWith is case sensitive so gott check both cases
808                            if (qfV.getFullName().endsWith("spare")) {
809                                    continue;
810                            }
811                            ncdff.addVariable(qfV.getGroup(), qfV);
812                            logger.trace("Adding QF product: " + qfV.getFullName());
813                            pathToProducts.add(qfV.getFullName());
814                            unsignedFlags.put(qfV.getFullName(), "true");
815                            unpackFlags.put(qfV.getFullName(), "false");
816                    }
817                    
818                        ncdfal.add((NetCDFFile) netCDFReader);
819                    }
820                    
821            } catch (Exception e) {
822                    logger.error("cannot create NetCDF reader for files selected");
823                    if (e.getMessage() != null && e.getMessage().equals("XML Product Profile Error")) {
824                            throw new VisADException("Unable to extract metadata from required XML Product Profile");
825                    }
826                    e.printStackTrace();
827            }
828            
829            // initialize the aggregation reader object
830            try {
831                    nppAggReader = new GranuleAggregation(ncdfal, pathToProducts, "Track", "XTrack", isEDR);
832                    ((GranuleAggregation) nppAggReader).setQfMap(qfMap);
833            } catch (Exception e) {
834                    throw new VisADException("Unable to initialize aggregation reader");
835            }
836    
837            // make sure we found valid data
838            if (pathToProducts.size() == 0) {
839                    throw new VisADException("No data found in files selected");
840            }
841            
842            logger.debug("Number of adapters needed: " + pathToProducts.size());
843            adapters = new MultiDimensionAdapter[pathToProducts.size()];
844            Hashtable<String, String[]> properties = new Hashtable<String, String[]>(); 
845            
846            Iterator<String> iterator = pathToProducts.iterator();
847            int pIdx = 0;
848            while (iterator.hasNext()) {
849                    String pStr = (String) iterator.next();
850                    logger.debug("Working on adapter number " + (pIdx + 1) + ": " + pStr);
851                    HashMap<String, Object> swathTable = SwathAdapter.getEmptyMetadataTable();
852                    HashMap<String, Object> spectTable = SpectrumAdapter.getEmptyMetadataTable();
853                    swathTable.put("array_name", pStr);
854                    swathTable.put("lon_array_name", pathToLon);
855                    swathTable.put("lat_array_name", pathToLat);
856                    swathTable.put("XTrack", "XTrack");
857                    swathTable.put("Track", "Track");
858                    swathTable.put("geo_Track", "Track");
859                    swathTable.put("geo_XTrack", "XTrack");
860                    swathTable.put("product_name", productName);
861                    
862                    // array_name common to spectrum table
863                    spectTable.put("array_name", pStr);
864                    spectTable.put("product_name", productName);
865                    logger.debug("Product Name: " + productName);
866                    logger.debug("is3D? : " + is3D);
867                    logger.debug("instrumentName: " + instrumentName.getStringValue());
868                    
869                    if (is3D) {
870    
871                            // 3D data is either ATMS, OMPS, or CrIS
872                            if ((instrumentName.getShortName() != null) && (instrumentName.getStringValue().equals("ATMS"))) {
873    
874                                    spectTable.put(SpectrumAdapter.channelIndex_name, "Channel");
875                            swathTable.put(SpectrumAdapter.channelIndex_name, "Channel");
876                            
877                            swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
878                            swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
879                            swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
880                            spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
881                            spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
882                            spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
883                            
884                            spectTable.put(SpectrumAdapter.channelType, "wavelength");
885                            spectTable.put(SpectrumAdapter.channels_name, "Channel");
886                        spectTable.put(SpectrumAdapter.x_dim_name, "XTrack");
887                        spectTable.put(SpectrumAdapter.y_dim_name, "Track");
888                        
889                                    int numChannels = JPSSUtilities.ATMSChannelCenterFrequencies.length;
890                            float[] bandArray = new float[numChannels];
891                            String[] bandNames = new String[numChannels];
892                            for (int bIdx = 0; bIdx < numChannels; bIdx++) {
893                                    bandArray[bIdx] = JPSSUtilities.ATMSChannelCenterFrequencies[bIdx];
894                                    bandNames[bIdx] = "Channel " + (bIdx + 1);
895                            }
896                            spectTable.put(SpectrumAdapter.channelValues, bandArray);
897                            spectTable.put(SpectrumAdapter.bandNames, bandNames);
898    
899                            } else {
900                                    if (instrumentName.getStringValue().equals("CrIS")) {
901                                            
902                                            swathTable.put("XTrack", "dim1");
903                                            swathTable.put("Track", "dim0");
904                                            swathTable.put("geo_XTrack", "dim1");
905                                            swathTable.put("geo_Track", "dim0");
906                                            swathTable.put("product_name", "CrIS_SDR");
907                                            swathTable.put(SpectrumAdapter.channelIndex_name, "dim3");
908                                            swathTable.put(SpectrumAdapter.FOVindex_name, "dim2"); 
909                                             
910                                            spectTable.put(SpectrumAdapter.channelIndex_name, "dim3");
911                                            spectTable.put(SpectrumAdapter.FOVindex_name, "dim2");
912                                    spectTable.put(SpectrumAdapter.x_dim_name, "dim1");
913                                    spectTable.put(SpectrumAdapter.y_dim_name, "dim0");
914                                    
915                                    } else if (instrumentName.getStringValue().contains("OMPS")) {
916                                            
917                                            spectTable.put(SpectrumAdapter.channelIndex_name, "Channel");
918                                    swathTable.put(SpectrumAdapter.channelIndex_name, "Channel");
919                                    
920                                    swathTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
921                                    swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
922                                    swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
923                                    spectTable.put("array_dimension_names", new String[] {"Track", "XTrack", "Channel"});
924                                    spectTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
925                                    spectTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
926                                    
927                                    spectTable.put(SpectrumAdapter.channelType, "wavelength");
928                                    spectTable.put(SpectrumAdapter.channels_name, "Channel");
929                            spectTable.put(SpectrumAdapter.x_dim_name, "XTrack");
930                            spectTable.put(SpectrumAdapter.y_dim_name, "Track");
931                            
932                                    int numChannels = 200;
933                                    if (instrumentName.getStringValue().equals("OMPS-TC")) {
934                                            numChannels = 260;
935                                    }
936                                    logger.debug("Setting up OMPS adapter, num channels: " + numChannels);
937                                    float[] bandArray = new float[numChannels];
938                                    String[] bandNames = new String[numChannels];
939                                    for (int bIdx = 0; bIdx < numChannels; bIdx++) {
940                                            bandArray[bIdx] = bIdx;
941                                            bandNames[bIdx] = "Channel " + (bIdx + 1);
942                                    }
943                                    spectTable.put(SpectrumAdapter.channelValues, bandArray);
944                                    spectTable.put(SpectrumAdapter.bandNames, bandNames);
945                                    
946                                    } else {
947                                            // sorry, if we can't id the instrument, we can't display the data!
948                                            throw new VisADException("Unable to determine instrument name");
949                                    }
950                            }
951    
952                    } else {
953                            swathTable.put("array_dimension_names", new String[] {"Track", "XTrack"});
954                            swathTable.put("lon_array_dimension_names", new String[] {"Track", "XTrack"});
955                            swathTable.put("lat_array_dimension_names", new String[] {"Track", "XTrack"});
956                    }
957                    
958                    swathTable.put("scale_name", "scale_factor");
959                    swathTable.put("offset_name", "add_offset");
960                    swathTable.put("fill_value_name", "_FillValue");
961                    swathTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1));
962                    spectTable.put("range_name", pStr.substring(pStr.indexOf(SEPARATOR_CHAR) + 1));
963                    
964                    // set the valid range hash if data is available
965                    if (nppPP != null) {
966                            if (nppPP.getRangeMin(pStr.substring(pStr.lastIndexOf(SEPARATOR_CHAR) + 1)) != null) {
967                                    swathTable.put("valid_range", "valid_range");
968                            }
969                    }
970                    
971                    String unsignedAttributeStr = unsignedFlags.get(pStr);
972                    if (unsignedAttributeStr.equals("true")) {
973                            swathTable.put("unsigned", unsignedAttributeStr);
974                    }
975                    
976                    String unpackFlagStr = unpackFlags.get(pStr);
977                    if (unpackFlagStr.equals("true")) {
978                            swathTable.put("unpack", "true");
979                    }
980                    
981                    // For Suomi NPP data, do valid range check AFTER applying scale/offset
982                    swathTable.put("range_check_after_scaling", "true");
983                    
984                    // pass in a GranuleAggregation reader...
985                    if (is3D) {
986                    if (instrumentName.getStringValue().equals("ATMS")) {
987                            adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
988                            SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable);
989                        DataCategory.createCategory("MultiSpectral");
990                        categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
991                            MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 
992                                    "BrightnessTemperature", "BrightnessTemperature", "SuomiNPP", "ATMS");
993                            msd.setInitialWavenumber(JPSSUtilities.ATMSChannelCenterFrequencies[0]);
994                            multiSpectralData.add(msd);
995                    } 
996                    if (instrumentName.getStringValue().equals("CrIS")) {
997                            adapters[pIdx] = new CrIS_SDR_SwathAdapter(nppAggReader, swathTable);
998                            CrIS_SDR_Spectrum csa = new CrIS_SDR_Spectrum(nppAggReader, spectTable);
999                        DataCategory.createCategory("MultiSpectral");
1000                        categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
1001                            MultiSpectralData msd = new CrIS_SDR_MultiSpectralData((CrIS_SDR_SwathAdapter) adapters[pIdx], csa); 
1002                        msd.setInitialWavenumber(csa.getInitialWavenumber());
1003                        msd_CrIS.add(msd);
1004                    }
1005                    if (instrumentName.getStringValue().contains("OMPS")) {
1006                            adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
1007                            SpectrumAdapter sa = new SpectrumAdapter(nppAggReader, spectTable);
1008                        DataCategory.createCategory("MultiSpectral");
1009                        categories = DataCategory.parseCategories("MultiSpectral;MultiSpectral;IMAGE");
1010                            MultiSpectralData msd = new MultiSpectralData((SwathAdapter) adapters[pIdx], sa, 
1011                                    "RadianceEarth", "RadianceEarth", "SuomiNPP", "OMPS");
1012                            msd.setInitialWavenumber(0);
1013                            multiSpectralData.add(msd);
1014                    } 
1015                    if (pIdx == 0) {
1016                            // generate preview image for ATMS and OMPS
1017                            if (! instrumentName.getStringValue().equals("CrIS")) {
1018                                    defaultSubset = multiSpectralData.get(pIdx).getDefaultSubset();
1019                                    try {
1020                                            previewImage = multiSpectralData.get(pIdx).getImage(defaultSubset);
1021                                    } catch (Exception e) {
1022                                            e.printStackTrace();
1023                                    }
1024                            }
1025                    }
1026                    
1027                    } else {
1028                            adapters[pIdx] = new SwathAdapter(nppAggReader, swathTable);
1029                            if (pIdx == 0) {
1030                                    defaultSubset = adapters[pIdx].getDefaultSubset();
1031                            }
1032                            categories = DataCategory.parseCategories("IMAGE");
1033                    }
1034                    pIdx++;
1035            }
1036    
1037            if (msd_CrIS.size() == 3) {
1038              try {
1039                MultiSpectralAggr aggr = new MultiSpectralAggr(msd_CrIS.toArray(new MultiSpectralData[msd_CrIS.size()]));
1040                aggr.setInitialWavenumber(902.25f);
1041                multiSpectralData.add(aggr);
1042                defaultSubset = ((MultiSpectralData)msd_CrIS.get(0)).getDefaultSubset();
1043                previewImage = ((MultiSpectralData)msd_CrIS.get(0)).getImage(defaultSubset);
1044              }
1045              catch (Exception e) {
1046                System.out.println(e);
1047              }
1048            }
1049    
1050            setProperties(properties);
1051        }
1052    
1053        public void initAfterUnpersistence() {
1054            try {
1055                if (getTmpPaths() != null) {
1056                    // New code for zipped bundles-
1057                    // we want 'sources' to point to wherever the zipped data was unpacked.
1058                    sources.clear();
1059                    // following PersistenceManager.fixBulkDataSources, get temporary data location
1060                    String zidvPath = 
1061                            McIDASV.getStaticMcv().getStateManager().
1062                            getProperty(IdvPersistenceManager.PROP_ZIDVPATH, "");
1063                    for (Object o : getTmpPaths()) {
1064                        String tempPath = (String) o;
1065                        // replace macro string with actual path
1066                        String expandedPath = tempPath.replace(PersistenceManager.MACRO_ZIDVPATH, zidvPath);
1067                        // we don't want to add nav files to this list!:
1068                        File f = new File(expandedPath);
1069                        if (!f.getName().matches(JPSSUtilities.SUOMI_GEO_REGEX)) {
1070                            sources.add(expandedPath);
1071                        }
1072                    }
1073                } else {
1074                    // leave in original unpersistence code - this will get run for unzipped bundles.
1075                    // TODO: do we need to handle the "Save with relative paths" case specially?
1076                        if (! oldSources.isEmpty()) {
1077                        sources.clear();
1078                        for (Object o : oldSources) {
1079                            sources.add((String) o);
1080                        }
1081                    }
1082                }
1083                oldSources.clear();
1084                    setup();
1085            } catch (Exception e) {
1086                    e.printStackTrace();
1087            }
1088        }
1089    
1090        /* (non-Javadoc)
1091             * @see edu.wisc.ssec.mcidasv.data.HydraDataSource#canSaveDataToLocalDisk()
1092             */
1093            @Override
1094            public boolean canSaveDataToLocalDisk() {
1095                    // At present, Suomi data is always data granules on disk
1096                    return true;
1097            }
1098    
1099            /* (non-Javadoc)
1100             * @see ucar.unidata.data.DataSourceImpl#saveDataToLocalDisk(java.lang.String, java.lang.Object, boolean)
1101             */
1102            @Override
1103            protected List saveDataToLocalDisk(String filePrefix, Object loadId,
1104                            boolean changeLinks) throws Exception {
1105                    // need to make a list of all data granule files
1106                    // PLUS all geolocation granule files, but only if accessed separate!
1107                    List<String> fileList = new ArrayList<String>();
1108                    for (Object o : sources) {
1109                            fileList.add((String) o);
1110                    }
1111                    for (String s : geoSources) {
1112                            fileList.add(s);
1113                    }
1114                    return fileList;
1115            }
1116    
1117            public List<String> getOldSources() {
1118                    return oldSources;
1119            }
1120    
1121            public void setOldSources(List<String> oldSources) {
1122                    this.oldSources = oldSources;
1123            }
1124    
1125            /**
1126         * Make and insert the <code>DataChoice</code>-s for this
1127         * <code>DataSource</code>.
1128         */
1129        
1130        public void doMakeDataChoices() {
1131            
1132            // special loop for CrIS, ATMS, and OMPS data
1133            if (multiSpectralData.size() > 0) {
1134                    for (int k = 0; k < multiSpectralData.size(); k++) {
1135                            MultiSpectralData adapter = multiSpectralData.get(k);
1136                            DataChoice choice = null;
1137                                    try {
1138                                            choice = doMakeDataChoice(k, adapter);
1139                                    msdMap.put(choice.getName(), adapter);
1140                                    addDataChoice(choice);
1141                                    } catch (Exception e) {
1142                                            e.printStackTrace();
1143                                    }
1144                    }
1145                    return;
1146            }
1147            // all other data (VIIRS and 2D EDRs)
1148            if (adapters != null) {
1149                    for (int idx = 0; idx < adapters.length; idx++) {
1150                            DataChoice choice = null;
1151                            try {
1152                                    choice = doMakeDataChoice(idx, adapters[idx].getArrayName());
1153                            } 
1154                            catch (Exception e) {
1155                                    e.printStackTrace();
1156                                    logger.error("doMakeDataChoice failed");
1157                            }
1158    
1159                            if (choice != null) {
1160                                    addDataChoice(choice);
1161                            }
1162                    }
1163            }
1164        }
1165    
1166        private DataChoice doMakeDataChoice(int idx, String var) throws Exception {
1167            String name = var;
1168            DataSelection dataSel = new MultiDimensionSubset(defaultSubset);
1169            Hashtable subset = new Hashtable();
1170            subset.put(new MultiDimensionSubset(), dataSel);
1171            DirectDataChoice ddc = new DirectDataChoice(this, idx, name, name, categories, subset);
1172            return ddc;
1173        }
1174        
1175        private DataChoice doMakeDataChoice(int idx, MultiSpectralData adapter) throws Exception {
1176            String name = adapter.getName();
1177            DataSelection dataSel = new MultiDimensionSubset(defaultSubset);
1178            Hashtable subset = new Hashtable();
1179            subset.put(MultiDimensionSubset.key, dataSel);
1180            subset.put(MultiSpectralDataSource.paramKey, adapter.getParameter());
1181            DirectDataChoice ddc = new DirectDataChoice(this, new Integer(idx), name, name, categories, subset);
1182            ddc.setProperties(subset);
1183            return ddc;
1184        }
1185    
1186        /**
1187         * Check to see if this <code>SuomiNPPDataSource</code> is equal to the object
1188         * in question.
1189         * @param o  object in question
1190         * @return true if they are the same or equivalent objects
1191         */
1192        
1193        public boolean equals(Object o) {
1194            if ( !(o instanceof SuomiNPPDataSource)) {
1195                return false;
1196            }
1197            return (this == (SuomiNPPDataSource) o);
1198        }
1199    
1200        public MultiSpectralData getMultiSpectralData() {
1201            return multiSpectralData.get(0);
1202        }
1203        
1204        public MultiSpectralData getMultiSpectralData(DataChoice choice) {
1205            return msdMap.get(choice.getName());
1206        }
1207    
1208        public String getDatasetName() {
1209          return filename;
1210        }
1211        
1212        /**
1213             * @return the qfMap
1214             */
1215            public HashMap<String, QualityFlag> getQfMap() {
1216                    return qfMap;
1217            }
1218    
1219            public void setDatasetName(String name) {
1220          filename = name;
1221        }
1222    
1223        public HashMap getSubsetFromLonLatRect(MultiDimensionSubset select, GeoSelection geoSelection) {
1224          GeoLocationInfo ginfo = geoSelection.getBoundingBox();
1225          return adapters[0].getSubsetFromLonLatRect(select.getSubset(), ginfo.getMinLat(), ginfo.getMaxLat(),
1226                                            ginfo.getMinLon(), ginfo.getMaxLon());
1227        }
1228    
1229        public synchronized Data getData(DataChoice dataChoice, DataCategory category,
1230                                    DataSelection dataSelection, Hashtable requestProperties)
1231                                    throws VisADException, RemoteException {
1232           return this.getDataInner(dataChoice, category, dataSelection, requestProperties);
1233        }
1234    
1235    
1236        protected Data getDataInner(DataChoice dataChoice, DataCategory category,
1237                                    DataSelection dataSelection, Hashtable requestProperties)
1238                                    throws VisADException, RemoteException {
1239    
1240            //- this hack keeps the HydraImageProbe from doing a getData()
1241            //- TODO: need to use categories?
1242            if (requestProperties != null) {
1243              if ((requestProperties.toString()).equals("{prop.requester=MultiSpectral}")) {
1244                return null;
1245              }
1246            }
1247    
1248            GeoLocationInfo ginfo = null;
1249            GeoSelection geoSelection = null;
1250            
1251            if ((dataSelection != null) && (dataSelection.getGeoSelection() != null)) {
1252              geoSelection = (dataSelection.getGeoSelection().getBoundingBox() != null) ? dataSelection.getGeoSelection() :
1253                                        dataChoice.getDataSelection().getGeoSelection();
1254            }
1255    
1256            if (geoSelection != null) {
1257              ginfo = geoSelection.getBoundingBox();
1258            }
1259    
1260            Data data = null;
1261            if (adapters == null) {
1262              return data;
1263            }
1264    
1265            MultiDimensionAdapter adapter = null;
1266            
1267            // pick the adapter with the same index as the current data choice
1268            int aIdx = 0;
1269            List<DataChoice> dcl = getDataChoices();
1270            for (DataChoice dc : dcl) {
1271                    if (dc.getName().equals(dataChoice.getName())) {
1272                            aIdx = dcl.indexOf(dc);
1273                            break;
1274                    }
1275            }
1276    
1277            adapter = adapters[aIdx];
1278    
1279            try {
1280                HashMap subset = null;
1281                if (ginfo != null) {
1282                    logger.debug("getting subset from lat-lon rect...");
1283                    subset = adapter.getSubsetFromLonLatRect(ginfo.getMinLat(), ginfo.getMaxLat(),
1284                                    ginfo.getMinLon(), ginfo.getMaxLon(),
1285                                    geoSelection.getXStride(),
1286                                    geoSelection.getYStride(),
1287                                    geoSelection.getZStride());
1288                }
1289                else {
1290    
1291                  MultiDimensionSubset select = null;
1292                  Hashtable table = dataChoice.getProperties();
1293                  Enumeration keys = table.keys();
1294                  while (keys.hasMoreElements()) {
1295                    Object key = keys.nextElement();
1296                    logger.debug("Key: " + key.toString());
1297                    if (key instanceof MultiDimensionSubset) {
1298                      select = (MultiDimensionSubset) table.get(key);
1299                    }
1300                  }  
1301                  subset = select.getSubset();
1302                  logger.debug("Subset size: " + subset.size());
1303    
1304                  if (dataSelection != null) {
1305                    Hashtable props = dataSelection.getProperties();
1306                    if (props != null) {
1307                      if (props.containsKey(SpectrumAdapter.channelIndex_name)) {
1308                              logger.debug("Props contains channel index key...");
1309                        double[] coords = (double[]) subset.get(SpectrumAdapter.channelIndex_name);
1310                        int idx = ((Integer) props.get(SpectrumAdapter.channelIndex_name)).intValue();
1311                        coords[0] = (double)idx;
1312                        coords[1] = (double)idx;
1313                        coords[2] = (double)1;
1314                      }
1315                    }
1316                  }
1317                }
1318    
1319                if (subset != null) {
1320                  data = adapter.getData(subset);
1321                  data = applyProperties(data, requestProperties, subset, aIdx);
1322                }
1323            } catch (Exception e) {
1324                e.printStackTrace();
1325                logger.error("getData exception e=" + e);
1326            }
1327            return data;
1328        }
1329    
1330        protected Data applyProperties(Data data, Hashtable requestProperties, HashMap subset, int adapterIndex) 
1331              throws VisADException, RemoteException {
1332          Data new_data = data;
1333    
1334          if (requestProperties == null) {
1335            new_data = data;
1336            return new_data;
1337          }
1338    
1339          return new_data;
1340        }
1341    
1342        protected void initDataSelectionComponents(
1343             List<DataSelectionComponent> components,
1344                 final DataChoice dataChoice) {
1345          
1346          if (System.getProperty("os.name").equals("Mac OS X") && hasImagePreview && hasChannelSelect) {
1347              try {
1348                      if (hasImagePreview) {
1349                              components.add(new ImageChannelSelection(new PreviewSelection(dataChoice, previewImage, null), new ChannelSelection(dataChoice)));
1350                      } 
1351                  if (hasChannelSelect) {
1352                      components.add(new ChannelSelection(dataChoice));
1353                  }
1354              } catch (Exception e) {
1355                e.printStackTrace();
1356              }
1357            }
1358          else {
1359              if (hasImagePreview) {
1360                      try {
1361                              FlatField image = (FlatField) dataChoice.getData(null);
1362                              components.add(new PreviewSelection(dataChoice, image, null));
1363                      } catch (Exception e) {
1364                              logger.error("Can't make PreviewSelection: " + e);
1365                              e.printStackTrace();
1366                      }
1367              }
1368              if (hasChannelSelect) {
1369                      try {
1370                              components.add(new ChannelSelection(dataChoice));
1371                      } 
1372                      catch (Exception e) {
1373                              e.printStackTrace();
1374                      }
1375              }
1376          }
1377          
1378        }
1379        
1380    }