001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2024
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 https://www.gnu.org/licenses/.
027 */
028
029package edu.wisc.ssec.mcidasv.data.adde;
030
031import java.awt.Component;
032import java.awt.Container;
033import java.awt.Dimension;
034import java.awt.event.ActionEvent;
035import java.awt.event.ActionListener;
036import java.io.File;
037import java.io.RandomAccessFile;
038import java.rmi.RemoteException;
039import java.text.SimpleDateFormat;
040import java.util.ArrayList;
041import java.util.Arrays;
042import java.util.Collection;
043import java.util.Collections;
044import java.util.Date;
045import java.util.Enumeration;
046import java.util.HashMap;
047import java.util.Hashtable;
048import java.util.Iterator;
049import java.util.List;
050import java.util.Map;
051import java.util.StringTokenizer;
052import java.util.TimeZone;
053import java.util.TreeMap;
054
055import javax.swing.BoxLayout;
056import javax.swing.JCheckBox;
057import javax.swing.JComponent;
058import javax.swing.JLabel;
059import javax.swing.JOptionPane;
060import javax.swing.JPanel;
061import javax.swing.JScrollPane;
062import javax.swing.JTabbedPane;
063
064import org.slf4j.Logger;
065import org.slf4j.LoggerFactory;
066
067import edu.wisc.ssec.mcidas.AREAnav;
068import edu.wisc.ssec.mcidas.AreaDirectory;
069import edu.wisc.ssec.mcidas.AreaDirectoryList;
070import edu.wisc.ssec.mcidas.AreaFile;
071import edu.wisc.ssec.mcidas.AreaFileException;
072import edu.wisc.ssec.mcidas.adde.AddeImageURL;
073import edu.wisc.ssec.mcidas.adde.AddeTextReader;
074import edu.wisc.ssec.mcidas.adde.AddeURL;
075import edu.wisc.ssec.mcidasv.data.GeoLatLonSelection;
076import edu.wisc.ssec.mcidasv.data.GeoPreviewSelection;
077import visad.CommonUnit;
078import visad.Data;
079import visad.DateTime;
080import visad.FlatField;
081import visad.FunctionType;
082import visad.MathType;
083import visad.RealType;
084import visad.Set;
085import visad.VisADException;
086import visad.data.DataRange;
087import visad.data.mcidas.AREACoordinateSystem;
088import visad.data.mcidas.AreaAdapter;
089import visad.georef.MapProjection;
090import visad.meteorology.ImageSequence;
091import visad.meteorology.ImageSequenceImpl;
092import visad.meteorology.SingleBandedImage;
093import ucar.nc2.iosp.mcidas.McIDASAreaProjection;
094import ucar.unidata.data.BadDataException;
095import ucar.unidata.data.CompositeDataChoice;
096import ucar.unidata.data.DataCategory;
097import ucar.unidata.data.DataChoice;
098import ucar.unidata.data.DataSelection;
099import ucar.unidata.data.DataSelectionComponent;
100import ucar.unidata.data.DataSourceDescriptor;
101import ucar.unidata.data.DerivedDataChoice;
102import ucar.unidata.data.DirectDataChoice;
103import ucar.unidata.data.GeoLocationInfo;
104import ucar.unidata.data.GeoSelection;
105import ucar.unidata.data.imagery.AddeImageDataSource;
106import ucar.unidata.data.imagery.AddeImageDescriptor;
107import ucar.unidata.data.imagery.AddeImageInfo;
108import ucar.unidata.data.imagery.BandInfo;
109import ucar.unidata.data.imagery.ImageDataset;
110import ucar.unidata.geoloc.LatLonPoint;
111import ucar.unidata.geoloc.ProjectionImpl;
112import ucar.unidata.idv.DisplayControl;
113import ucar.unidata.idv.MapViewManager;
114import ucar.unidata.util.GuiUtils;
115import ucar.unidata.util.IOUtil;
116import ucar.unidata.util.LogUtil;
117import ucar.unidata.util.PollingInfo;
118import ucar.unidata.util.StringUtil;
119import ucar.unidata.util.ThreeDSize;
120import ucar.unidata.util.TwoFacedObject;
121import ucar.visad.UtcDate;
122import ucar.visad.Util;
123import ucar.visad.data.AreaImageFlatField;
124
125/**
126 * Abstract DataSource class for images files.
127 */
128
129public class AddeImageParameterDataSource extends AddeImageDataSource {
130
131    private static final Logger logger = LoggerFactory.getLogger(AddeImageParameterDataSource.class);
132
133    /**
134     * Public keys for server, group, dataset, user, project.
135     */
136    public final static String SIZE_KEY = "size";
137    public final static String PLACE_KEY = "place";
138    public final static String LATLON_KEY = "latlon";
139    public final static String LINELE_KEY = "linele";
140    public final static String MAG_KEY = "mag";
141    public final static String BAND_KEY = "band";
142    public final static String BANDINFO_KEY = "bandinfo";
143    public final static String UNIT_KEY = "unit";
144    public final static String PREVIEW_KEY = "preview";
145    public final static String SPAC_KEY = "spac";
146    public final static String NAV_KEY = "nav";
147    public final static String AUX_KEY = "aux";
148    public final static String DOC_KEY = "doc";
149    public final static String SPACING_BRIT = "1";
150    public final static String SPACING_NON_BRIT = "4";
151
152    /** The first projection we find */
153//    protected ProjectionImpl sampleProjection;
154    public MapProjection sampleMapProjection;
155
156    /* ADDE request string */
157    private String source;
158    private String baseSource;
159
160    /* properties for this data source */
161    private Hashtable sourceProps;
162    private Hashtable selectionProps;
163
164    private int lineResolution;
165    private int elementResolution;
166    private float lRes;
167    private float eRes;
168    private int lineMag = 1;
169    private int elementMag = 1;
170
171    private GeoSelection lastGeoSelection;
172    private DataChoice lastChoice = null;
173    private Boolean showPreview = Boolean.FALSE;
174    private FlatField previewImage = null;
175    private MapProjection previewProjection;
176    private Hashtable initProps;
177
178    private AreaDirectory previewDir = null;
179    private AREAnav previewNav = null;
180    private boolean haveDataSelectionComponents = false;
181
182    private GeoPreviewSelection previewSel;
183    private GeoLatLonSelection laLoSel;
184
185    private String choiceName;
186
187    private String saveCoordType;
188    private String savePlace;
189    private double saveLat;
190    private double saveLon;
191    private int saveNumLine;
192    private int saveNumEle;
193    private int saveLineMag;
194    private int saveEleMag;
195    private Boolean saveShowPreview;
196
197    private String displaySource;
198
199    // keep track of extra info for derived fields, since they may be a mix of bands and resolutions
200    boolean isDerived = false;
201    HashMap<Integer, Double> derivedBandLineRes = new HashMap<Integer, Double>();
202    HashMap<Integer, Double> derivedBandElemRes = new HashMap<Integer, Double>();
203    HashMap<Integer, Integer> derivedBandMagFactor = new HashMap<Integer, Integer>();
204
205    protected List<DataChoice> stashedChoices = null;
206    private List iml = new ArrayList();
207    private List saveImageList = new ArrayList();
208
209    private int previewLineRes = 1;
210    private int previewEleRes = 1;
211    
212    // Do binary search on preview bounds for GEO sensors to speed up overall response time
213    boolean isGeoSensor = false;
214
215    // Also need to know when dealing with a sensor where sectors shift around spatially
216    boolean isABISensor = false;
217
218    /** Whether or not this DataSource was loaded from a bundle. */
219    private boolean fromBundle = false;
220    
221    /** Are any of the data choices based upon remote files? */
222    private boolean hasRemoteChoices = false;
223
224    private Map<String, AreaDirectory> requestIdToDirectory = new HashMap<String, AreaDirectory>();
225    
226    // TJJ Aug 2020
227    // Detect domain shift - for ex ABI MESOs can abruptly move
228    // If we detect one, load the later, shifted image(s) earth-centered relative to the earlier
229    boolean domainShiftDetected = false;
230    boolean domainShiftNoticeDerivedShown = false;
231    boolean domainShiftNoticeTargetShown = false;
232
233    public AddeImageParameterDataSource() {} 
234
235    /**
236     * Creates a {@code AddeImageParameterDataSource} with a single ADDE URL.
237     * <b>Note:</b> the URLs should point at {@literal "image"} data.
238     * 
239     * @param descriptor {@link ucar.unidata.data.DataSourceDescriptor DataSourceDescriptor} for this data source.
240     * @param image ADDE URL
241     * @param properties The properties for this data source.
242     * 
243     * @throws VisADException
244     */
245    public AddeImageParameterDataSource(DataSourceDescriptor descriptor, String image,
246                               Hashtable properties)
247            throws VisADException {
248        super(descriptor, new String[] { image }, properties);
249        logger.trace("1: desc={}, image={}, properties={}", new Object[] { descriptor, image, properties });
250    }
251
252    /**
253     * Create a new AddeImageParameterDataSource with an array of ADDE URL strings.
254     * <b>Note:</b> the URLs should point at {@literal "image"} data.
255     * 
256     * @param descriptor {@link ucar.unidata.data.DataSourceDescriptor DataSourceDescriptor} for this data source.
257     * @param images Array of ADDE URLs.
258     * @param properties Properties for this data source.
259     * 
260     * @throws VisADException
261     */
262    public AddeImageParameterDataSource(DataSourceDescriptor descriptor, String[] images,
263                           Hashtable properties) throws VisADException {
264        super(descriptor, images, properties);
265        logger.trace("2: desc={}, images={}, properties={}", new Object[] { descriptor, images, properties });
266    }
267
268    /**
269     * Creates a new {@code AddeImageParameterDataSource} with an 
270     * {@link java.util.List List} of ADDE URL strings.
271     * <b>Note:</b> the URLs should point at {@literal "image"} data.
272     * 
273     * @param descriptor {@link ucar.unidata.data.DataSourceDescriptor DataSourceDescriptor} for this data source.
274     * @param images {@code List} of ADDE URL strings.
275     * @param properties Properties for this data source.
276     * 
277     * @throws VisADException
278     */
279    public AddeImageParameterDataSource(DataSourceDescriptor descriptor, List images,
280                           Hashtable properties) throws VisADException {
281        super(descriptor, images, properties);
282        logger.trace("3: desc={}, images={}, properties={}", new Object[] { descriptor, images, properties });
283    }
284
285    /**
286     * Create a new AddeImageParameterDataSource with the given dataset.
287     * 
288     * @param descriptor {@link ucar.unidata.data.DataSourceDescriptor DataSourceDescriptor} for this data source.
289     * @param ids Dataset.
290     * @param properties Properties for this data source.
291     * 
292     * @throws VisADException
293     */
294    
295    public AddeImageParameterDataSource(DataSourceDescriptor descriptor, ImageDataset ids,
296                           Hashtable properties) throws VisADException {
297        super(descriptor, ids, properties);
298        logger.trace("4: desc={}, ids={}, properties={}", new Object[] { descriptor, ids, properties });
299        this.sourceProps = properties;
300        if (properties.containsKey((Object)PREVIEW_KEY)) {
301            this.showPreview = (Boolean)(properties.get((Object)PREVIEW_KEY));
302            saveShowPreview = showPreview;
303        } else {
304            if (saveShowPreview != null) {
305                showPreview = saveShowPreview;
306            }
307        }
308        
309        List descs = ids.getImageDescriptors();
310        AddeImageDescriptor aid = (AddeImageDescriptor)descs.get(0);
311        this.source = aid.getSource();
312        if (this.source.contains("localhost")) {
313            AreaDirectory areaDirectory = aid.getDirectory();
314            if (!sourceProps.containsKey((Object)UNIT_KEY)) {
315                if (!sourceProps.containsKey((Object)BAND_KEY)) {
316                    String calType = areaDirectory.getCalibrationType();
317                    if (!calType.equals("RAW")) {
318                        sourceProps.put(UNIT_KEY, calType);
319                        int[] bandNums = areaDirectory.getBands();
320                        String bandString = new Integer(bandNums[0]).toString();
321                        sourceProps.put(BAND_KEY, bandString);
322                    }
323                }
324            }
325        }
326        setMag();
327        getAreaDirectory(properties);
328    }
329
330    @Override protected void propertiesChanged() {
331        logger.trace("fired");
332        super.propertiesChanged();
333    }
334
335    @Override protected boolean initDataFromPollingInfo() {
336        boolean result = super.initDataFromPollingInfo();
337        logger.trace("result={}", result);
338        return result;
339    }
340
341    @Override protected boolean isPolling() {
342        boolean result = super.isPolling();
343        logger.trace("isPolling={}", result);
344        return result;
345    }
346
347    @Override public void setPollingInfo(PollingInfo value) {
348        logger.trace("value={}", value);
349        super.setPollingInfo(value);
350    }
351
352    @Override protected boolean hasPollingInfo() {
353        boolean result = super.hasPollingInfo();
354        logger.trace("hasPollingInfo={}", result);
355        return result;
356    }
357
358    @Override public PollingInfo getPollingInfo() {
359        PollingInfo result = super.getPollingInfo();
360        logger.trace("getPollingInfo={}", result);
361        return result;
362    }
363
364    @Override public void initAfterUnpersistence() {
365        logger.trace("unbundled!");
366        super.initAfterUnpersistence();
367
368        if (this.sourceProps.containsKey(PREVIEW_KEY)) {
369            this.showPreview = (Boolean)this.sourceProps.get(PREVIEW_KEY);
370            if (this.showPreview == null) {
371                this.showPreview = Boolean.FALSE;
372            }
373            this.saveShowPreview = this.showPreview;
374        }
375        
376        this.fromBundle = true;
377        List<AddeImageDescriptor> descriptors = (List<AddeImageDescriptor>) getImageList();
378        this.source = descriptors.get(0).getSource(); // TODO: why not use the source from
379                                                      // each AddeImageDescriptor?
380        for (AddeImageDescriptor descriptor : descriptors) {
381            if (!isFromFile(descriptor)) {
382                this.hasRemoteChoices = true;
383                break;
384            }
385        }
386    }
387
388    @Override public boolean canSaveDataToLocalDisk() {
389        return true;
390    }
391
392    private Hashtable<String, DataSelection> choiceToSel = new Hashtable<String, DataSelection>();
393
394    public DataSelection getSelForChoice(final DataChoice choice) {
395        String key = choice.getName();
396        return choiceToSel.get(key);
397    }
398    public boolean hasSelForChoice(final DataChoice choice) {
399        String key = choice.getName();
400        return choiceToSel.containsKey(key);
401    }
402    public void putSelForChoice(final DataChoice choice, final DataSelection sel) {
403        String key = choice.getName();
404        choiceToSel.put(key, sel);
405    }
406
407    /**
408     * Save files to local disk
409     *
410     * @param prefix destination dir and file prefix
411     * @param loadId For JobManager
412     * @param changeLinks Change internal file references
413     *
414     * @return Files copied
415     *
416     * @throws Exception On badness
417     */
418    
419    @Override protected List saveDataToLocalDisk(String prefix, Object loadId, boolean changeLinks) throws Exception {
420        logger.trace("prefix={} loadId={} changeLinks={}", new Object[] { prefix, loadId, changeLinks });
421        final List<JCheckBox> checkboxes = new ArrayList<JCheckBox>();
422        List categories = new ArrayList();
423        Hashtable catMap = new Hashtable();
424        Hashtable currentDataChoices = new Hashtable();
425
426        List displays = getIdv().getDisplayControls();
427        for (int i = 0; i < displays.size(); i++) {
428            List dataChoices = ((DisplayControl)displays.get(i)).getDataChoices();
429            if (dataChoices == null) {
430                continue;
431            }
432            List finalOnes = new ArrayList();
433            for (int j = 0; j < dataChoices.size(); j++) {
434                ((DataChoice)dataChoices.get(j)).getFinalDataChoices(finalOnes);
435            }
436            for (int dcIdx = 0; dcIdx < finalOnes.size(); dcIdx++) {
437                DataChoice dc = (DataChoice)finalOnes.get(dcIdx);
438                if (!(dc instanceof DirectDataChoice)) {
439                    continue;
440                }
441                DirectDataChoice ddc = (DirectDataChoice) dc;
442                if (ddc.getDataSource() != this) {
443                    continue;
444                }
445                currentDataChoices.put(ddc.getName(), "");
446            }
447        }
448
449        for (int i = 0; i < dataChoices.size(); i++) {
450            DataChoice dataChoice = (DataChoice) dataChoices.get(i);
451            if (!(dataChoice instanceof DirectDataChoice)) {
452                continue;
453            }
454
455            // skip over datachoices that the user has not already loaded.
456            // (but fill the "slot" with null (it's a hack to signify that 
457            // the "download" loop should skip over the data choice associated 
458            // with this slot)
459            if (!currentDataChoices.containsKey(dataChoice.getName())) {
460                checkboxes.add(null); // 
461                continue;
462            }
463
464            String label = dataChoice.getDescription();
465            if (label.length() > 30) {
466                label = label.substring(0, 29) + "...";
467            }
468            JCheckBox cbx =
469                new JCheckBox(label, 
470                              currentDataChoices.get(dataChoice.getName())
471                              != null);
472            ThreeDSize size = (ThreeDSize)dataChoice.getProperty(SIZE_KEY);
473            cbx.setToolTipText(dataChoice.getName());
474            checkboxes.add(cbx);
475            DataCategory dc = dataChoice.getDisplayCategory();
476            if (dc == null) {
477                dc = DataCategory.createCategory(DataCategory.CATEGORY_IMAGE);
478            }
479            List comps = (List)catMap.get(dc);
480            if (comps == null) {
481                comps = new ArrayList();
482                catMap.put(dc, comps);
483                categories.add(dc);
484            }
485            comps.add(cbx);
486            comps.add(GuiUtils.filler());
487            if (size != null) {
488                JLabel sizeLabel = GuiUtils.rLabel(size.getSize() + "  ");
489                sizeLabel.setToolTipText(size.getLabel());
490                comps.add(sizeLabel);
491            } else {
492                comps.add(new JLabel(""));
493            }
494        }
495        final JCheckBox allCbx = new JCheckBox("Select All");
496        allCbx.addActionListener(new ActionListener() {
497            public void actionPerformed(ActionEvent ae) {
498                for (JCheckBox cbx : checkboxes) {
499                    if (cbx != null) {
500                        cbx.setSelected(allCbx.isSelected());
501                    }
502                }
503            }
504        });
505
506        JTabbedPane tab = new JTabbedPane(JTabbedPane.LEFT);
507
508        for (int i = 0; i < categories.size(); i++) {
509            List comps = (List) catMap.get(categories.get(i));
510            JPanel innerPanel = GuiUtils.doLayout(comps, 3, GuiUtils.WT_NYN, GuiUtils.WT_N);
511            JScrollPane sp = new JScrollPane(GuiUtils.top(innerPanel));
512            sp.setPreferredSize(new Dimension(500, 400));
513            JPanel top = GuiUtils.right(GuiUtils.rLabel("  "));
514            JComponent inner = GuiUtils.inset(GuiUtils.topCenter(top, sp), 5);
515            tab.addTab(categories.get(i).toString(), inner);
516        }
517
518        JComponent contents = tab;
519        contents = GuiUtils.topCenter(
520            GuiUtils.inset(
521                GuiUtils.leftRight(
522                    new JLabel("Select the fields to download"),
523                    allCbx), 5), contents);
524        JLabel label = new JLabel(getNameForDataSource(this, 50, true));
525        contents = GuiUtils.topCenter(label, contents);
526        contents = GuiUtils.inset(contents, 5);
527        if (!GuiUtils.showOkCancelDialog(null, "", contents, null)) {
528            return null;
529        }
530
531        // iterate through user's selection to build list of things to download
532        List<String> realUrls = new ArrayList<String>();
533        List<AddeImageDescriptor> descriptorsToSave = new ArrayList<AddeImageDescriptor>();
534        List<BandInfo> bandInfos = (List<BandInfo>)getProperty(PROP_BANDINFO, (Object)null);
535        List<BandInfo> savedBands = new ArrayList<BandInfo>();
536        for (int i = 0; i < dataChoices.size(); i++) {
537            DataChoice dataChoice = (DataChoice)dataChoices.get(i);
538            if (!(dataChoice instanceof DirectDataChoice)) {
539                continue;
540            }
541            JCheckBox cbx = (JCheckBox)checkboxes.get(i);
542            if (cbx == null || !cbx.isSelected()) {
543                continue;
544            }
545
546            if (dataChoice.getDataSelection() == null) {
547                dataChoice.setDataSelection(getSelForChoice(dataChoice));
548            }
549            logger.trace("selected choice={} id={}", dataChoice.getName(), dataChoice.getId());
550            List<AddeImageDescriptor> descriptors = getDescriptors(dataChoice, dataChoice.getDataSelection());
551            logger.trace("descriptors={}", descriptors);
552            
553            BandInfo bandInfo;
554            Object dataChoiceId = dataChoice.getId();
555            if (dataChoiceId instanceof BandInfo) {
556                bandInfo = (BandInfo)dataChoiceId;
557            } else {
558                bandInfo = bandInfos.get(0);
559            }
560            String preferredUnit = bandInfo.getPreferredUnit();
561            List<TwoFacedObject> filteredCalUnits = new ArrayList<TwoFacedObject>();
562            for (TwoFacedObject tfo : (List<TwoFacedObject>)bandInfo.getCalibrationUnits()) {
563                if (preferredUnit.equals(tfo.getId())) {
564                    filteredCalUnits.add(tfo);
565                }
566            }
567            bandInfo.setCalibrationUnits(filteredCalUnits);
568            savedBands.add(bandInfo);
569
570            DataSelection selection = dataChoice.getDataSelection();
571            if (selection == null) {
572                if (getSelForChoice(dataChoice) != null) {
573                    selection = getSelForChoice(dataChoice);
574                } else {
575                    selection = getDataSelection();
576                }
577            }
578
579            Hashtable selectionProperties = selection.getProperties();
580//            Hashtable selectionProperties;
581//            if (selection != null) {
582//                selectionProperties = selection.getProperties();
583//            } else {
584//                DataSelection sel = this.getDataSelection();
585//                selectionProperties = new Hashtable();
586//            }
587            logger.trace("bandinfo.getUnit={} selection props={}", bandInfo.getPreferredUnit(), selectionProperties);
588            for (AddeImageDescriptor descriptor : descriptors) {
589//                AddeImageInfo aii = (AddeImageInfo)descriptor.getImageInfo().clone();
590                if (!isFromFile(descriptor)) {
591                    String src = descriptor.getSource();
592                    logger.trace("src before={}", src);
593                    src = replaceKey(src, AddeImageURL.KEY_UNIT, bandInfo.getPreferredUnit());
594                    if (selectionProperties.containsKey(AddeImageURL.KEY_PLACE)) {
595                        src = replaceKey(src, AddeImageURL.KEY_PLACE, selectionProperties.get(AddeImageURL.KEY_PLACE));
596                    }
597                    if (selectionProperties.containsKey(AddeImageURL.KEY_LATLON)) {
598                        src = replaceKey(src, AddeImageURL.KEY_LINEELE, AddeImageURL.KEY_LATLON, selectionProperties.get(AddeImageURL.KEY_LATLON));
599                    }
600                    if (selectionProperties.containsKey(AddeImageURL.KEY_LINEELE)) {
601                        src = removeKey(src, AddeImageURL.KEY_LATLON);
602                        src = replaceKey(src, AddeImageURL.KEY_LINEELE, selectionProperties.get(AddeImageURL.KEY_LINEELE));
603                    }
604                    if (selectionProperties.containsKey(AddeImageURL.KEY_MAG)) {
605                        src = replaceKey(src, AddeImageURL.KEY_MAG, selectionProperties.get(AddeImageURL.KEY_MAG));
606                    }
607                    if (selectionProperties.containsKey(AddeImageURL.KEY_SIZE)) {
608                        src = replaceKey(src, AddeImageURL.KEY_SIZE, selectionProperties.get(AddeImageURL.KEY_SIZE));
609                    }
610                    logger.trace("src after={}", src);
611                    descriptor.setSource(src);
612                }
613                descriptorsToSave.add(descriptor);
614            }
615//          descriptorsToSave.addAll(descriptors);
616        }
617        if (!savedBands.isEmpty()) {
618            setProperty(PROP_BANDINFO, savedBands);
619        }
620        if (descriptorsToSave.isEmpty()) {
621            return null;
622        }
623
624        // Start the load, showing the dialog
625        List<String> suffixes = new ArrayList<String>();
626        SimpleDateFormat sdf = new SimpleDateFormat("_" + DATAPATH_DATE_FORMAT);
627        sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
628        for (int i = 0; i < descriptorsToSave.size(); i++) {
629            AddeImageDescriptor descriptor = descriptorsToSave.get(i);
630            AddeImageInfo aii = descriptor.getImageInfo();
631            DateTime dttm = (DateTime)timeMap.get(descriptor.getSource());
632            if (dttm != null) {
633                suffixes.add(sdf.format(ucar.visad.Util.makeDate(dttm)) + ".area");
634            } else if (aii != null) {
635                String suffix = "_Band"+aii.getBand()+"_Unit"+aii.getUnit()+"_Pos"+i+".area";
636                suffixes.add(suffix);
637                logger.trace("test suffix={}", suffix);
638            } else {
639                suffixes.add(i + ".area");
640            }
641            realUrls.add(descriptor.getSource());
642        }
643        logger.trace("urls={}", realUrls);
644        logger.trace("prefix={}", prefix);
645        logger.trace("suffixes={}", suffixes);
646        logger.trace("loadId={}", loadId);
647        List newFiles = IOUtil.writeTo(realUrls, prefix, suffixes, loadId);
648        logger.trace("files={}", newFiles);
649        if (newFiles == null) {
650            logger.trace("failed while in writeTo?");
651            return null;
652        } else {
653            logger.trace("finished writeTo!");
654        }
655        if (changeLinks) {
656            imageList = newFiles;
657        }
658
659        // write 0 as the first word
660        for (int i = 0; i < newFiles.size(); i++) {
661            try {
662                RandomAccessFile to = new RandomAccessFile((String)newFiles.get(i), "rw");
663                to.seek(0);
664                to.writeInt(0);
665                to.close();
666            } catch (Exception e) {
667                logger.error("unable to set first word to zero", e);
668            }
669        }
670
671
672//        if (geoSubset != null) {
673//            geoSubset.clearStride();
674//            geoSubset.setBoundingBox(null);
675//            if (geoSelectionPanel != null) {
676//                geoSelectionPanel.initWith(doMakeGeoSelectionPanel());
677//            }
678//        }
679
680//        List newFiles = Misc.newList(path);
681//        if (changeLinks) {
682//            //Get rid of the resolver URL
683//            getProperties().remove(PROP_RESOLVERURL);
684//            setNewFiles(newFiles);
685//        }
686//        
687        logger.trace("returning={}", newFiles);
688        return newFiles;
689    }
690
691    @Override protected String getDataPrefix() {
692        String tmp = StringUtil.replace(getName(), ' ', "");
693        tmp = StringUtil.replace(tmp, '/', "");
694        tmp = StringUtil.replace(tmp, "(AllBands)", "");
695        tmp = IOUtil.cleanFileName(tmp);
696        logger.trace("data prefix={}", tmp);
697        return tmp;
698    }
699    
700    /**
701     * A utility method that helps us deal with legacy bundles that used to
702     * have String file names as the id of a data choice.
703     *
704     * @param object     May be an AddeImageDescriptor (for new bundles) or a
705     *                   String that is converted to an image descriptor.
706     * @return The image descriptor.
707     */
708    @Override public AddeImageDescriptor getDescriptor(Object object) {
709//        logger.trace("--------------------");
710        if (object == null) {
711//            logger.trace("null obj");
712            return null;
713        }
714        if (object instanceof DataChoice) {
715            object = ((DataChoice)object).getId();
716            logger.trace("datachoice getId={}", object);
717        }
718        if (object instanceof ImageDataInfo) {
719            int index = ((ImageDataInfo) object).getIndex();
720            if (index < myDataChoices.size()) {
721                DataChoice dc = (DataChoice)myDataChoices.get(index);
722                Object tmpObject = dc.getId();
723                if (tmpObject instanceof ImageDataInfo) {
724//                    logger.trace("returning imagedatainfo");
725                    return ((ImageDataInfo)tmpObject).getAid();
726                }
727            }
728//            logger.trace("invalid idx for imagedatainfo? (idx={} vs size={})", index, myDataChoices.size());
729            return null;
730            //            return ((ImageDataInfo) object).getAid();
731        }
732
733        if (object instanceof AddeImageDescriptor) {
734//            logger.trace("already addeimagedesc! desc={}", object);
735            return (AddeImageDescriptor)object;
736        }
737        AddeImageDescriptor tmp = new AddeImageDescriptor(object.toString());
738//        logger.trace("return descriptor={}", tmp);
739//        logger.trace("--------------------");
740        return tmp;
741    }
742
743    /**
744     *  Overwrite base class  method to return the name of this class.
745     *
746     *  @return The name.
747     */
748    public String getImageDataSourceName() {
749        return "Adde Image Data Source (Parameter)";
750    }
751
752    private void setMag() {
753        Object magKey = (Object)"mag";
754        if (sourceProps.containsKey(magKey)) {
755            String magVal = (String)(sourceProps.get(magKey));
756            String[] magVals = magVal.split(" ");
757            this.lineMag = new Integer(magVals[0]).intValue();
758            this.elementMag = new Integer(magVals[1]).intValue();
759        }
760    }
761
762    private void getAreaDirectory(Hashtable properties) {
763        String addeCmdBuff = source;
764        if (addeCmdBuff.contains("BAND=")) {
765            String bandStr = getKey(addeCmdBuff, "BAND");
766            if (bandStr.length() == 0) {
767                addeCmdBuff = replaceKey(addeCmdBuff, "BAND", "1");
768            }
769        }
770        if (addeCmdBuff.contains("MAG=")) {
771            String[] segs = addeCmdBuff.split("MAG=");
772            String seg0 = segs[0];
773            String seg1 = segs[1];
774            int indx = seg1.indexOf("&");
775            seg1 = seg1.substring(indx);
776            String magString = lineMag + " " + elementMag;
777            addeCmdBuff = seg0 + "MAG=" + magString + seg1;
778        }
779        addeCmdBuff = addeCmdBuff.replace("imagedata", "imagedir");
780        AreaDirectoryList dirList = null;
781        try {
782            dirList = new AreaDirectoryList(addeCmdBuff);
783        } catch (Exception e) {
784            try {
785                List<BandInfo> bandInfos = (List<BandInfo>)getProperty(PROP_BANDINFO, (Object)null);
786                BandInfo bi = bandInfos.get(0);
787//                String bandStr = new Integer(bi.getBandNumber()).toString();
788                addeCmdBuff = replaceKey(addeCmdBuff, "BAND", bi.getBandNumber());
789                dirList = new AreaDirectoryList(addeCmdBuff);
790            } catch (Exception eOpen) {
791                setInError(true);
792                logger.error("problem opening AREA file", eOpen);
793            }
794        }
795
796        try {
797            List areaDirs = dirList.getDirs();
798            AreaDirectory ad = (AreaDirectory)areaDirs.get(0);
799            float[] res = getLineEleResolution(ad);
800            float resol = res[0];
801            if (this.lineMag < 0) {
802                resol *= Math.abs(this.lineMag);
803            }
804//            this.lineResolution = ad.getValue(11);
805            this.lineResolution = ad.getValue(AreaFile.AD_LINERES);
806            this.lRes = resol;
807            resol = res[1];
808            if (this.elementMag < 0) {
809                resol *= Math.abs(this.elementMag);
810            }
811//            this.elementResolution = ad.getValue(12);
812            this.elementResolution = ad.getValue(AreaFile.AD_ELEMRES);
813            this.eRes = resol;
814        } catch (Exception e) {
815            setInError(true);
816            logger.error("getting area directory", e);
817        }
818        baseSource = addeCmdBuff;
819    }
820
821    protected void initDataSelectionComponents(
822        List components, final DataChoice dataChoice)
823    {
824        if (fromBundle && !hasRemoteChoices) {
825            components.add(new BundlePreviewSelection("Region (Disabled)"));
826            components.add(new BundlePreviewSelection("Advanced (Disabled)"));
827            return;
828        }
829
830        getIdv().showWaitCursor();
831
832        boolean hasImagePreview = true;
833        if (this.showPreview == null) {
834            this.showPreview = true;
835        }
836        boolean basically = false;
837        if (this.lastChoice != null) {
838            basically = dataChoice.basicallyEquals(this.lastChoice);
839        }
840        logger.trace("dataChoice={}", dataChoice);
841        // check for comps and whether or not dataChoice is hooping right back into line
842        if (this.haveDataSelectionComponents && dataChoice.equals(this.lastChoice)) {
843            try {
844                // did the datachoice ever actually get data?
845                if (dataChoice.getDataSelection() == null) {
846                    if (!basically) {
847                        logger.trace("creating geolatlonselection");
848                        this.laLoSel = new GeoLatLonSelection(this,
849                                         dataChoice, this.initProps, this.previewProjection,
850                                         previewDir, previewNav);
851
852                        this.lineMag = this.laLoSel.getLineMag();
853                        this.elementMag = this.laLoSel.getElementMag();
854
855                    /* DAVEP: Force preview on. "No preview" means blank image */
856//                    this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage,
857//                                     this.laLoSel, this.previewProjection,
858//                                     this.lineMag, this.elementMag, this.showPreview);
859                        logger.trace("1: creating geopreviewselection: has geoprevsel: {}", this.previewSel!=null);
860                        this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage,
861                            this.laLoSel, this.previewProjection,
862                            this.lineMag, this.elementMag, true);
863                    }
864//                    this.lineMag = this.laLoSel.getLineMag();
865//                    this.elementMag = this.laLoSel.getElementMag();
866//
867//                    /* DAVEP: Force preview on. "No preview" means blank image */
868////                    this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage,
869////                                     this.laLoSel, this.previewProjection,
870////                                     this.lineMag, this.elementMag, this.showPreview);
871//                    logger.trace("1: creating geopreviewselection: has geoprevsel: {}", this.previewSel!=null);
872//                    this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage,
873//                            this.laLoSel, this.previewProjection,
874//                            this.lineMag, this.elementMag, true);
875                }
876                components.add(this.previewSel);
877                components.add(this.laLoSel);
878            } catch (Exception e) {
879                logger.error("error while repeating addition of selection components", e);
880                getIdv().showNormalCursor();
881            }
882        } else {
883            try {
884                hasImagePreview = makePreviewImage(dataChoice);
885                if (basically) {
886                    getSaveComponents();
887                }
888            } catch (Exception e) {
889                logger.error("Preview image Exception: " + e.getMessage());
890                JLabel label = new JLabel("Can't make preview image");
891                JPanel contents = GuiUtils.top(GuiUtils.inset(label, label.getText().length() + 12));
892                GuiUtils.showOkDialog(null, "No Preview Image", contents, null);
893                getIdv().showNormalCursor();
894                logger.error("problem creating preview image", e);
895                return;
896            }
897            this.lastChoice = dataChoice;
898            if (hasImagePreview) {
899                try {
900                    String magStr = getKey(baseSource, MAG_KEY);
901                    String saveMagStr = magStr;
902                    String[] vals = StringUtil.split(magStr, " ", 2);
903                    Integer iVal = new Integer(vals[0]);
904                    int lMag = iVal.intValue() * -1;
905                    if (lMag == -1) {
906                        lMag = 1;
907                    }
908                    iVal = new Integer(vals[1]);
909                    int eMag = iVal.intValue() * -1;
910                    if (eMag == -1) {
911                        eMag = 1;
912                    }
913                    magStr = lMag + " " + eMag;
914                    replaceKey(MAG_KEY, magStr);
915//                    String saveStr = baseSource;
916//                    if (!showPreview) {
917//                        replaceKey(SIZE_KEY, "2 2");
918//                    }
919                    AreaAdapter aa = null;
920                    AREACoordinateSystem acs = null;
921                    try {
922                        logger.trace("creating AreaFile from src={}", baseSource);
923                        if (showPreview) {
924                            aa = new AreaAdapter(baseSource, false);
925                            this.previewImage = (FlatField)aa.getImage();
926                        } else {
927                            this.previewImage = Util.makeField(0, 1, 1, 0, 1, 1, 0, "TEMP");
928                        }
929                        
930                        AreaFile af = new AreaFile(baseSource);
931                        previewNav = af.getNavigation();
932                        AreaDirectory ad = af.getAreaDirectory();
933                        this.lineResolution = ad.getValue(AreaFile.AD_LINERES);
934                        this.elementResolution = ad.getValue(AreaFile.AD_ELEMRES);
935                        acs = new AREACoordinateSystem(af);
936                    } catch (Exception e) {
937                        String excp = e.toString();
938                        int indx = excp.lastIndexOf(":");
939                        String errorText = excp.substring(indx+1);
940                        JLabel label = new JLabel(errorText);
941                        JPanel contents = GuiUtils.top(GuiUtils.inset(label, label.getText().length() + 12));
942                        GuiUtils.showOkDialog(null, "Can't Make Geographical Selection Tabs", contents, null);
943                        getIdv().showNormalCursor();
944                        logger.error("problem creating preview image", e);
945                        return;
946                    }
947                    this.initProps = new Hashtable();
948                    Enumeration propEnum = sourceProps.keys();
949                    for (int i = 0; propEnum.hasMoreElements(); i++) {
950                        String key = propEnum.nextElement().toString();
951                        Object val = sourceProps.get(key);
952                        key = key.toUpperCase();
953                        if (val instanceof String) {
954                            String str = (String)val;
955                            val = (Object)(str.toUpperCase());
956                        }
957                        this.initProps.put(key,val);
958                    }
959                    replaceKey(MAG_KEY, saveMagStr);
960                    magStr = getKey(baseSource, MAG_KEY);
961                    vals = StringUtil.split(magStr, " ", 2);
962                    iVal = new Integer(vals[0]);
963                    lMag = iVal.intValue();
964                    iVal = new Integer(vals[1]);
965                    eMag = iVal.intValue();
966
967                    this.initProps.put("LRES", String.valueOf((this.lRes)));
968                    this.initProps.put("ERES", String.valueOf((this.eRes)));
969                    this.initProps.put("PLRES", String.valueOf((this.previewLineRes)));
970                    this.initProps.put("PERES", String.valueOf((this.previewEleRes)));
971                    this.previewProjection = (MapProjection)acs;
972
973                    String coordType = "";
974                    double coords[] = { 0.0, 0.0 };
975                    
976                    logger.trace("basically={} laLoSel==null?={}", basically, (this.laLoSel==null));
977                    if (!basically) {
978                        if (this.laLoSel != null) {
979                            coordType = this.laLoSel.getCoordinateType();
980                            if (coordType.equals(this.laLoSel.getLatLonType())) {
981                                coords[0] = this.laLoSel.getLatitude();
982                                coords[1] = this.laLoSel.getLongitude();
983                            } else {
984                                coords[0] = (double)this.laLoSel.getLine();
985                                coords[1] = (double)this.laLoSel.getElement();
986                            }
987
988                            // turns out that laLoSel is reused for datachoices
989                            // from the same source. if you don't update laLoSel's
990                            // dataChoice, it'll apply whatever data selection
991                            // you set up... to the first data choice that you
992                            // loaded! (and causing an NPE when attempting to
993                            // bundle the dataselection for the newly-selected
994                            // datachoice.
995                            this.previewSel.setDataChoice(dataChoice);
996                            this.laLoSel.setDataChoice(dataChoice);
997                            this.laLoSel.setPreviewLineRes(this.previewLineRes);
998                            this.laLoSel.setPreviewEleRes(this.previewEleRes);
999                            this.laLoSel.update(previewDir, this.previewProjection, previewNav,
1000                                           coordType, coords);
1001                            
1002                        } else {
1003                            this.laLoSel = new GeoLatLonSelection(this, 
1004                                          dataChoice, this.initProps, this.previewProjection,
1005                                          previewDir, previewNav);
1006                            this.lineMag = this.laLoSel.getLineMag();
1007                            this.elementMag = this.laLoSel.getElementMag();
1008                        }
1009                    } else {
1010                        if (this.laLoSel != null) {
1011                            this.previewSel.setDataChoice(dataChoice);
1012                            this.laLoSel.setDataChoice(dataChoice);
1013                        }
1014                    }
1015                    /* DAVEP: Force preview on. "No preview" means blank image */
1016//                    this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage, 
1017//                                     this.laLoSel, this.previewProjection,
1018//                                     this.lineMag, this.elementMag, this.showPreview);
1019                    logger.trace("even reaching this point?");
1020                    this.previewSel = new GeoPreviewSelection(this, dataChoice, this.previewImage, 
1021                            this.laLoSel, this.previewProjection,
1022                            this.lineMag, this.elementMag, showPreview);
1023                    logger.trace("how about this one?");
1024                } catch (Exception e) {
1025                    logger.error("problem making selection components", e);
1026                    getIdv().showNormalCursor();
1027                }
1028                this.haveDataSelectionComponents = true;
1029//                replaceKey(MAG_KEY, (Object)(this.lineMag + " " + this.elementMag));
1030                replaceKey(MAG_KEY, (this.lineMag + " " + this.elementMag));
1031                components.add(this.previewSel);
1032                components.add(this.laLoSel);
1033            }
1034        }
1035        if (this.previewSel != null) {
1036            this.previewSel.initBox();
1037        }
1038        getIdv().showNormalCursor();
1039    }
1040
1041    /**
1042     * A hook to allow this data source to add data selection components
1043     * to the IDV field selector
1044     *
1045     * @param dataChoice the data choice
1046     *
1047     * @return list of components
1048     */
1049//    @Override public List<DataSelectionComponent> getDataSelectionComponents(DataChoice dataChoice) {
1050////        List<DataSelectionComponent> dataSelectionComponents = new ArrayList<DataSelectionComponent>();
1051////        initDataSelectionComponents(dataSelectionComponents, dataChoice);
1052////        return dataSelectionComponents;
1053//        return new ArrayList<DataSelectionComponent>();
1054//    }
1055    
1056    private boolean makePreviewImage(DataChoice dataChoice) {
1057        logger.trace("Starting with dataChoice={}", dataChoice);
1058        getIdv().showWaitCursor();
1059        
1060        // For dealing with derived fields - will need to know which bands are involved
1061        // They may be a mix of base resolutions
1062        ArrayList<Integer> derivedBands = new ArrayList<Integer>();
1063        
1064        // TJJ Inq #2181 Feb 2020
1065        // If derived, figure out which bands are involved. Open to better ideas here :-)
1066        if (dataChoice instanceof DerivedDataChoice) {
1067            derivedBandLineRes.clear();
1068            derivedBandElemRes.clear();
1069            derivedBandMagFactor.clear();
1070            isDerived = true;
1071            Hashtable ht = ((DerivedDataChoice) dataChoice).getUserSelectedChoices();
1072            java.util.Set<String> set = ht.keySet();
1073            for (String s : set) {
1074                DataChoice dc = (DataChoice) ht.get(s);
1075                logger.debug("Data choice String ID " + dc.getStringId());
1076                logger.debug("Data choice Name " + dc.getName());
1077                String dcName = dc.getName();
1078                // Pull out band number, definitely hacky but don't know a better way
1079                int idxLo = dcName.indexOf("Band") + 4;
1080                int idxHi = dcName.indexOf('_', idxLo);
1081                String bandStr = dcName.substring(idxLo, idxHi);
1082                logger.debug("Data choice Band " + bandStr);
1083                derivedBands.add(Integer.parseInt(bandStr));
1084                Hashtable dcProps = dc.getProperties();
1085            }
1086        } else {
1087            isDerived = false;
1088        }
1089        
1090        boolean msgFlag = false;
1091        showPreview = saveShowPreview;
1092        List<BandInfo> bandInfos = (List<BandInfo>)getProperty(PROP_BANDINFO, (Object) null);
1093        BandInfo bi = null;
1094
1095        String saveBand = getKey(source, BAND_KEY);
1096
1097        int bandIdx = 0;
1098        int sensorID = -1;
1099
1100        logger.trace("band index stuff: saveBand={}, bandIdx={}, source={}", new Object[] { saveBand, bandIdx, source });
1101        List<TwoFacedObject> calList = null;
1102        try {
1103            Object dcObj = dataChoice.getId();
1104            if (dcObj instanceof BandInfo) {
1105                bi = (BandInfo) dcObj;
1106                Integer bandInt = new Integer(bandInfos.indexOf(dcObj)+1);
1107                saveBand = bandInt.toString();
1108            } else {
1109                msgFlag = true;
1110                bi = bandInfos.get(bandIdx);
1111                this.showPreview = false;
1112                // If derived, use band representing best available res
1113                if (isDerived) {
1114                    this.showPreview = true;
1115                    double bestRes = Double.MAX_VALUE;
1116                    int tmpIdx = 0;
1117                    for (BandInfo bandInfo : bandInfos) {
1118                        AreaDirectory ad = (AreaDirectory) allBandDirs.get(bandInfo.getBandNumber());
1119                        double lineRes = ad.getCenterLatitudeResolution();
1120                        double elemRes = ad.getCenterLongitudeResolution();
1121                        if (derivedBands.contains(bandInfo.getBandNumber())) {
1122                            int tmpBand = bandInfo.getBandNumber();
1123                            derivedBandLineRes.put(tmpBand, lineRes);
1124                            derivedBandElemRes.put(tmpBand, elemRes);
1125                        }
1126                        if (lineRes < bestRes && (derivedBands.contains(bandInfo.getBandNumber()))) {
1127                            bestRes = lineRes;
1128                            bandIdx = tmpIdx;
1129                            logger.debug("Derived field, updated bandIdx to: " + bandIdx);
1130                            bi = bandInfo;
1131                            saveBand = String.valueOf(bi.getBandNumber());
1132                            logger.debug("Updated bandInfo: " + bi);
1133                        }
1134                        tmpIdx++;
1135                    }
1136                    // Now that we've got all the derived bands and resolutions,
1137                    // set multipliers for line/elem ranges based on mixtures of resolutions
1138                    boolean allThree = false;
1139                    boolean twoLower = false;
1140                    boolean twoHigher = false;
1141                    boolean allSame = true;
1142                    Collection<Double> resVals = (Collection<Double>) derivedBandLineRes.values();
1143                    if (resVals.contains(0.5)) {
1144                        if (resVals.contains(1.0)) {
1145                            if (resVals.contains(2.0)) {
1146                                allThree = true;
1147                                allSame = false;
1148                            } else {
1149                                twoLower = true;
1150                                allSame = false;
1151                            }
1152                        }
1153                    } else {
1154                        if (resVals.contains(1.0)) {
1155                            if (resVals.contains(2.0)) {
1156                                twoHigher = true;
1157                                allSame = false;
1158                            }
1159                        }
1160                    }
1161
1162                    for (Integer bandNum : derivedBandLineRes.keySet()) {
1163
1164                        Double bandRes = derivedBandLineRes.get(bandNum);
1165                        if (allSame) {
1166                            derivedBandMagFactor.put(bandNum, 1);
1167                        }
1168                        if (twoLower) {
1169                            if (bandRes < 1) {
1170                                derivedBandMagFactor.put(bandNum, 1);
1171                            } else {
1172                                derivedBandMagFactor.put(bandNum, 2);
1173                            }
1174                        }
1175                        if (twoHigher) {
1176                            if (bandRes > 1) {
1177                                derivedBandMagFactor.put(bandNum, 2);
1178                            } else {
1179                                derivedBandMagFactor.put(bandNum, 1);
1180                            }
1181                        }
1182                        if (allThree) {
1183                            if (bandRes > 1) {
1184                                derivedBandMagFactor.put(bandNum, 4);
1185                            } else {
1186                                if (bandRes < 1) {
1187                                    derivedBandMagFactor.put(bandNum, 1);
1188                                } else {
1189                                    derivedBandMagFactor.put(bandNum, 2);
1190                                }
1191                            }
1192                        }
1193                    }
1194
1195                    // Print all mags for verification
1196                    derivedBandMagFactor.entrySet().forEach(entry -> {
1197                        logger.debug("Derived Mag Band : " + entry.getKey() + " Mag Value : " + entry.getValue());
1198                    });
1199
1200                }
1201            }
1202            // pull out the list of cal units, we'll need for type check later...
1203            calList = bi.getCalibrationUnits();
1204            
1205            // TJJ Dec 2017
1206            // Kinda hacky (but then again so is this entire class) :-/
1207            // Do our GEO speedup code for known GEO sensor IDs
1208            sensorID = bi.getSensor();
1209            if (sensorIsGEO(sensorID)) {
1210                isGeoSensor = true;
1211            }
1212            if (sensorIsABI(sensorID)) {
1213                isABISensor = true;
1214            }
1215
1216            logger.trace("replacing band: new={} from={}", bi.getBandNumber(), source);
1217            source = replaceKey(source, BAND_KEY, bi.getBandNumber());
1218            // if we're replacing the band, replace cal type with preferred type for that band
1219            logger.trace("replacing unit: new={} from={}", bi.getPreferredUnit(), source);
1220            source = replaceKey(source, UNIT_KEY, bi.getPreferredUnit());
1221        } catch (Exception excp) {
1222            handlePreviewImageError(1, excp);
1223        }
1224        String name = dataChoice.getName();
1225        int idx = name.lastIndexOf('_');
1226        String unit = name.substring(idx + 1);
1227
1228        // if this is not a valid cal unit (e.g. could be set to a plugin formula name)
1229        // set it to something valid
1230        boolean validCal = false;
1231        for (TwoFacedObject tfo : calList) {
1232            if (unit.equals((String) tfo.getId())) {
1233                validCal = true;
1234                break;
1235            }
1236        }
1237        if (!validCal) {
1238            unit = bi.getPreferredUnit();
1239        }
1240
1241        if (getKey(source, UNIT_KEY).length() == 0) {
1242            logger.trace("non-empty unit, replacing: new={} from={}", unit, source);
1243//            source = replaceKey(source, UNIT_KEY, (Object)(unit));
1244            source = replaceKey(source, UNIT_KEY, unit);
1245        }
1246
1247        AddeImageDescriptor aid = null;
1248        while (aid == null) {
1249            try {
1250                logger.trace("creating new AddeImageDescriptor from {}", this.source);
1251                aid = new AddeImageDescriptor(this.source);
1252            } catch (Exception excp) {
1253                msgFlag = true;
1254                if (bandIdx > (bandInfos.size() - 1)) {
1255                        getIdv().showNormalCursor();
1256                    return false;
1257                }
1258                
1259                bi = bandInfos.get(bandIdx);
1260                logger.trace("replacing band: new={} from={}", bi.getBandNumber(), source);
1261//                source = replaceKey(source, BAND_KEY, (Object)(bi.getBandNumber()));
1262                source = replaceKey(source, BAND_KEY, bi.getBandNumber());
1263                ++bandIdx;
1264            }
1265        }
1266//        previewDir = getPreviewDirectory(aid);
1267        AddeImageDescriptor previewDescriptor = getPreviewDirectory(aid, dataChoice);
1268        previewDir = previewDescriptor.getDirectory();
1269
1270        // On the off chance user is working with ABI MESO or AHI TARGET sectors,
1271        // *and* they have polling on, we need to turn auto-set projection off
1272        // since the domain can shift around
1273
1274        if (isABISensor) {
1275            String memo = previewDir.getMemoField().toLowerCase();
1276            boolean isMesoOrTarget = false;
1277            if ((memo.contains("meso")) || (memo.contains("targ"))) isMesoOrTarget = true;
1278            logger.info("Preview Directory memo field: " + memo);
1279            if (isPolling() && isMesoOrTarget) {
1280                MapViewManager mvm = (MapViewManager) getIdv().getViewManager();
1281                mvm.setUseProjectionFromData(false);
1282                boolean offScreen = getIdv().getArgsManager().getIsOffScreen();
1283                if (! domainShiftNoticeTargetShown) {
1284                    if (! offScreen) {
1285                        String msg = "You currently have polling active, and are working with\n" +
1286                                "a targeted sector (e.g. ABI MESO). Since these domains can\n" +
1287                                "shift geographically, auto-set projection has been turned off.\n";
1288                        Object[] params = { msg };
1289                        JOptionPane.showMessageDialog(null, params, "Notice", JOptionPane.OK_OPTION);
1290                        domainShiftNoticeTargetShown = true;
1291                    } else {
1292                        logger.warn("Note: Polling with shifting sector - auto-set projection turned off");
1293                    }
1294                }
1295            }
1296        }
1297
1298        logger.trace("using previewDir={}", previewDir);
1299//        try {
1300//            logger.trace("preview areadir: stlines={} stelements={} lines={} elements={}", new Object[] { previewDir.getValue(AreaFile.AD_STLINE), previewDir.getValue(AreaFile.AD_STELEM), previewDir.getLines(), previewDir.getElements() });
1301//        } catch (Exception e) {
1302//            logger.error("error logging areadir preview", e);
1303//        }
1304        int eMag = 1;
1305        int lMag = 1;
1306        int eSize = 1;
1307        int lSize = 1;
1308        try {
1309            int plMag = 1;
1310            int peMag = 1;
1311            Object magKey = (Object) "mag";
1312            if (sourceProps.containsKey(magKey)) {
1313                String magVal = (String)(sourceProps.get(magKey));
1314                String[] magVals = magVal.split(" ");
1315                peMag = new Integer(magVals[0]).intValue();
1316                plMag = new Integer(magVals[1]).intValue();
1317            }
1318            double feSize = (double) previewDir.getElements();
1319            double flSize = (double) previewDir.getLines();
1320            double feMag = (double) peMag;
1321            double flMag = (double) plMag;
1322            if (feSize > flSize) {
1323                feMag = feSize / 525.0;
1324                flMag = feMag * (double) plMag / (double) peMag;
1325            } else {
1326                flMag = flSize/500.0;
1327                feMag = flMag * (double) peMag / (double) plMag;
1328            }
1329            eMag = (int) Math.ceil(feMag);
1330            lMag = (int) Math.ceil(flMag);
1331        } catch(Exception excp) {
1332           handlePreviewImageError(3, excp);
1333        }
1334        if (eMag < 1) eMag = 1;
1335        if (lMag < 1) lMag = 1;
1336
1337        eSize = 525;
1338        lSize = 500;
1339        if ((baseSource == null) || msgFlag) {
1340            logger.trace("replacing\nbaseSource={}\nsource={}", baseSource, source);
1341            baseSource = source;
1342        }
1343        this.previewLineRes = lMag;
1344        this.previewEleRes = eMag;
1345        String uLStr = "0 0 F";
1346        // MJH getting uLStr from previewDir here breaks Himawari-8,
1347        // and is apparently unnecessary in general, so just use "0 0 F" always.
1348        // try {
1349        //     int startLine = previewDir.getValue(AreaFile.AD_STLINE);
1350        //     int startEle = previewDir.getValue(AreaFile.AD_STELEM);
1351        //     uLStr = startLine + " " + startEle + " I";
1352        // } catch (Exception e) {
1353        // }
1354//        String src = aid.getSource();
1355        String src = previewDescriptor.getSource();
1356        logger.trace("building preview request from src={}", src);
1357        
1358        src = removeKey(src, LATLON_KEY);
1359        src = replaceKey(src, LINELE_KEY, uLStr);
1360        src = replaceKey(src, PLACE_KEY, "ULEFT");
1361        src = replaceKey(src, SIZE_KEY,(lSize + " " + eSize));
1362        src = replaceKey(src, MAG_KEY, (lMag + " " + eMag));
1363        src = replaceKey(src, BAND_KEY, bi.getBandNumber());
1364        src = replaceKey(src, UNIT_KEY, unit);
1365//        if (aid.getIsRelative()) {
1366//            logger.trace("injecting POS={}", aid.getRelativeIndex());
1367//            src = replaceKey(src, "POS", (Object)aid.getRelativeIndex());
1368//        }
1369//        if (previewDescriptor.getIsRelative()) {
1370//            logger.trace("inject POS={} into src={}", previewDescriptor.getRelativeIndex(), src);
1371//            src = replaceKey(src, "POS", (Object)previewDescriptor.getRelativeIndex());
1372//            src = replaceKey(src, "POS", previewDescriptor.getRelativeIndex());
1373//        }
1374
1375        logger.trace("creating AddeImageDescriptor from src={}", src);
1376        try {
1377            aid = new AddeImageDescriptor(src);
1378        } catch (Exception excp) {
1379            handlePreviewImageError(4, excp);
1380            src = replaceKey(src, BAND_KEY, saveBand);
1381            aid = new AddeImageDescriptor(src);
1382            src = replaceKey(src, BAND_KEY, bi.getBandNumber());
1383        }
1384        if (msgFlag && (!"ALL".equals(saveBand))) {
1385            src = replaceKey(src, BAND_KEY, saveBand);
1386        }
1387        logger.trace("overwriting\nbaseSource={}\nsrc={}", baseSource, src);
1388        baseSource = src;
1389        getIdv().showNormalCursor();
1390        return true;
1391    }
1392
1393    /**
1394     * Return true if the Sensor is ABI variant (ABI, AHI, AMI)
1395     * These sensors have MESO sectors which move around geospatially
1396     *
1397     * @param sensorID McIDAS Sensor Source number.
1398     *  See https://www.ssec.wisc.edu/mcidas/doc/users_guide/2017.2/app_c-1.html
1399     * @return true if ID matches a defined ABI sensor
1400     */
1401
1402    private boolean sensorIsABI(int sensorID) {
1403
1404        boolean isABI = false;
1405
1406        // GOES 16 - 19
1407        if (sensorID == 186) isABI = true;
1408        if (sensorID == 188) isABI = true;
1409        if (sensorID == 190) isABI = true;
1410        if (sensorID == 192) isABI = true;
1411
1412        // Himawari-8 and Himawari-9
1413        if (sensorID == 86) isABI = true;
1414        if (sensorID == 286) isABI = true;
1415        if (sensorID == 287) isABI = true;
1416        if (sensorID == 288) isABI = true;
1417        if (sensorID == 289) isABI = true;
1418
1419        return isABI;
1420    }
1421
1422    /**
1423     * Return true if the Sensor is Geostationary
1424     * 
1425     * @param sensorID McIDAS Sensor Source number.
1426     *  See https://www.ssec.wisc.edu/mcidas/doc/users_guide/2017.2/app_c-1.html
1427     * @return true if ID matches a defined GEO sensor
1428     */
1429
1430    private boolean sensorIsGEO(int sensorID) {
1431        boolean isGEO = false;
1432        
1433        // early GEO through FY
1434        if ((sensorID >= 12) && (sensorID <= 40)) isGEO = true; 
1435        
1436        // Meteosat
1437        if ((sensorID >= 51) && (sensorID <= 58)) isGEO = true;
1438        if (sensorID == 354) isGEO = true;
1439
1440        // GOES 8 - 12
1441        if ((sensorID >= 70) && (sensorID <= 79)) isGEO = true;
1442
1443        // GMS, MTSAT (which is actually pre-8 Himawari), H-8/9
1444        if ((sensorID >= 82) && (sensorID <= 86)) isGEO = true;
1445        if ((sensorID >= 286) && (sensorID <= 288)) isGEO = true;
1446
1447        // Feng Yun
1448        if ((sensorID >= 95) && (sensorID <= 97)) isGEO = true;
1449        if ((sensorID >= 275) && (sensorID <= 277)) isGEO = true;
1450
1451        // GOES 13 - 19
1452        // NOTE: through all four GOES-R series satellites
1453        if ((sensorID >= 180) && (sensorID <= 193)) isGEO = true;
1454
1455        // Kalpana and INSAT
1456        if ((sensorID >= 230) && (sensorID <= 232)) isGEO = true;
1457
1458        // COMS
1459        if (sensorID == 250) isGEO = true;
1460        
1461        return isGEO;
1462    }
1463
1464    /**
1465     * Show the given error to the user. 
1466     *
1467     * @param excp The exception
1468     */
1469    protected void handlePreviewImageError(int flag, Exception excp) {
1470        getIdv().showNormalCursor();
1471        LogUtil.userErrorMessage("Error in makePreviewImage  e=" + flag + " " + excp);
1472    }
1473
1474    public static String removeKey(String src, String key) {
1475        String returnString = src;
1476        key = key.toUpperCase() + '=';
1477        if (returnString.contains(key)) {
1478            String[] segs = returnString.split(key);
1479            String seg0 = segs[0];
1480            String seg1 = segs[1];
1481            int indx = seg1.indexOf('&');
1482            if (indx >= 0) {
1483                seg1 = seg1.substring(indx + 1);
1484            }
1485            returnString = seg0 + seg1;
1486        }
1487        return returnString;
1488    }
1489
1490    public static String replaceKey(String sourceUrl, String key, Object value) {
1491        String returnString = sourceUrl;
1492
1493        // make sure we got valid key/value pair
1494        if ((key == null) || (value == null)) {
1495            return returnString;
1496        }
1497
1498        key = key.toUpperCase() + '=';
1499        String strValue = value.toString();
1500        if (returnString.contains(key)) {
1501            String[] segs = returnString.split(key);
1502            String seg0 = segs[0];
1503            String seg1 = segs[1];
1504            int indx = seg1.indexOf('&');
1505            if (indx < 0) {
1506                seg1 = "";
1507            } else if (indx > 0) {
1508                seg1 = seg1.substring(indx);
1509            }
1510            returnString = seg0 + key + strValue + seg1;
1511        } else {
1512            returnString = returnString + '&' + key + strValue;
1513        }
1514
1515        // if key is for cal units, and it was changed to BRIT,
1516        // must change the spacing key too 
1517        if ((key.equals(UNIT_KEY + '=')) && ("BRIT".equals(strValue))) {
1518            returnString = replaceKey(returnString, SPAC_KEY, SPAC_KEY, SPACING_BRIT);
1519        } else {
1520            returnString = replaceKey(returnString, SPAC_KEY, SPAC_KEY, SPACING_NON_BRIT); 
1521        }
1522        return returnString;
1523    }
1524
1525    public static String replaceKey(String src, String oldKey, String newKey, Object value) {
1526        String returnString = src;
1527        oldKey = oldKey.toUpperCase() + '=';
1528        newKey = newKey.toUpperCase() + '=';
1529        if (returnString.contains(oldKey)) {
1530            String[] segs = returnString.split(oldKey);
1531            String seg0 = segs[0];
1532            String seg1 = segs[1];
1533            int indx = seg1.indexOf('&');
1534            if (indx < 0) {
1535                seg1 = "";
1536            } else if (indx > 0) {
1537                seg1 = seg1.substring(indx);
1538            }
1539            returnString = seg0 + newKey + value.toString() + seg1;
1540        }
1541        else {
1542            returnString = returnString + '&' + newKey + value.toString();
1543        }
1544        return returnString;
1545    }
1546
1547    private <T> void replaceKey(String key, T value) {
1548        baseSource = replaceKey(baseSource, key, value);
1549    }
1550
1551    public static String getKey(String src, String key) {
1552        String returnString = "";
1553        key = key.toUpperCase() + '=';
1554        if (src.contains(key)) {
1555            String[] segs = src.split(key);
1556            segs = segs[1].split("&");
1557            returnString = segs[0];
1558        }
1559        return returnString;
1560    }
1561
1562
1563    /**
1564     * Create the set of {@link ucar.unidata.data.DataChoice} that represent
1565     * the data held by this data source.  We create one top-level
1566     * {@link ucar.unidata.data.CompositeDataChoice} that represents
1567     * all of the image time steps. We create a set of children
1568     * {@link ucar.unidata.data.DirectDataChoice}, one for each time step.
1569     */
1570    
1571    public void doMakeDataChoices() {
1572        super.doMakeDataChoices();
1573        List<BandInfo> bandInfos = (List<BandInfo>) getProperty(PROP_BANDINFO, (Object) null);
1574        String name = "";
1575        if (this.choiceName != null) {
1576            name = this.choiceName;
1577        }
1578        if (name.length() != 0) {
1579            logger.trace("already have a name={}", name);
1580            return;
1581        }
1582        if (!sourceProps.containsKey(UNIT_KEY)) {
1583            logger.trace("sourceProps has no unit key={}", sourceProps);
1584            return;
1585        }
1586        BandInfo bi = null;
1587        if (sourceProps.containsKey(BAND_KEY)) {
1588            int bandProp = new Integer((String)(sourceProps.get(BAND_KEY))).intValue();
1589            int bandIndex = BandInfo.findIndexByNumber(bandProp, bandInfos);
1590            bi = (BandInfo)bandInfos.get(bandIndex);
1591            if (sourceProps.containsKey(UNIT_KEY)) {
1592                bi.setPreferredUnit((String)(sourceProps.get(UNIT_KEY)));
1593            } else {
1594                bi.setPreferredUnit("");
1595            }
1596            name = makeBandParam(bi);
1597        }
1598        else if (sourceProps.containsKey(BANDINFO_KEY)) {
1599            ArrayList al = (ArrayList) sourceProps.get(BANDINFO_KEY);
1600            bi = (BandInfo) al.get(0);
1601            name = makeBandParam(bi);
1602        }
1603        if (stashedChoices != null) {
1604            int numChoices = stashedChoices.size();
1605            for (int i = 0; i < numChoices; i++) {
1606               DataChoice choice = (DataChoice) stashedChoices.get(i);
1607               if (name.equals(choice.getName())) {
1608                   setProperty(PROP_DATACHOICENAME, choice.getName());
1609               }
1610            }
1611        }
1612    }
1613
1614    /**
1615     * Overridden so that McIDAS-V can <i>attempt</i> to return the correct
1616     * {@code DataSelection} for the current {@code DataChoice}.
1617     */
1618    
1619    @Override public DataSelection getDataSelection() {
1620        DataSelection tmp;
1621
1622        if (this.laLoSel != null) {
1623            logger.trace("* mcv getSelForChoice: choice='{}'", this.laLoSel.getDataChoice());
1624            tmp = this.getSelForChoice(this.laLoSel.getDataChoice());
1625        } else {
1626            logger.trace("* idvland getDataSelection laLoSel=null: {}; choiceToSel=null: {}", (this.laLoSel==null), (this.choiceToSel==null));
1627            tmp = super.getDataSelection();
1628        }
1629//        if (this.laLoSel == null || this.choiceToSel == null || !this.choiceToSel.containsKey(this.laLoSel.getDataChoice())) {
1630//            logger.trace("* idvland getDataSelection laLoSel=null: {}; choiceToSel=null: {}", (this.laLoSel==null), (this.choiceToSel==null));
1631//            tmp = super.getDataSelection();
1632//        } else if (this.laLoSel != null) {
1633//            logger.trace("* mcv getSelForChoice");
1634//            tmp = this.getSelForChoice(this.laLoSel.getDataChoice());
1635//        }
1636        if (tmp != null) {
1637            logger.trace("return selection props={} geo={}", tmp.getProperties(), tmp.getGeoSelection());
1638        } else {
1639            logger.trace("return selection props=null geo=null choiceToSel={} :(", this.choiceToSel);
1640        }
1641        return tmp;
1642    }
1643
1644    /**
1645     * Overridden so that McIDAS-V can associate this data source's current 
1646     * {@code DataChoice} with the given {@code DataSelection}.
1647     */
1648    
1649    @Override public void setDataSelection(DataSelection s) {
1650
1651        GeoSelection tmp = null;
1652        if (s != null) {
1653            tmp = s.getGeoSelection();
1654        }
1655        if (tmp != null && this.laLoSel != null) {
1656            GeoLocationInfo bbox = tmp.getBoundingBox();
1657            GeoLocationInfo laloBbox = this.laLoSel.getGeoLocationInfo();
1658            tmp.setBoundingBox(laloBbox);
1659            logger.trace("incoming bbox={} laLo bbox={}", bbox, laloBbox);
1660        }
1661
1662        super.setDataSelection(s);
1663
1664        if (this.laLoSel != null) {
1665            this.putSelForChoice(this.laLoSel.getDataChoice(), s);
1666        } else {
1667            logger.trace("laLoSel is null; s={}", s);
1668        }
1669
1670    }
1671    
1672    /**
1673     * Insert the new DataChoice into the dataChoice list.
1674     *
1675     * @param choice   new choice to add
1676     */
1677    
1678    protected void addDataChoice(DataChoice choice) {
1679        logger.trace("choice={}", choice);
1680        super.addDataChoice(choice);
1681        if (stashedChoices == null) {
1682            stashedChoices = new ArrayList();
1683        }
1684        stashedChoices.add(choice);
1685    }
1686
1687    /**
1688     * Checks to see if a given {@code AddeImageDescriptor} is based upon a 
1689     * local (or remote) file.
1690     * 
1691     * <p>The check is pretty simple: is {@code descriptor.getSource()} a valid
1692     * path?
1693     * 
1694     * @param descriptor {@code AddeImageDescriptor} of questionable origins. Shouldn't be {@code null}.
1695     * 
1696     * @return {@code true} if {@code descriptor}'s source is a valid path.
1697     */
1698    
1699    public static boolean isFromFile(final AddeImageDescriptor descriptor) {
1700        return new File(descriptor.getSource()).exists();
1701    }
1702
1703    /**
1704     * Create the actual data represented by the given
1705     * {@link ucar.unidata.data.DataChoice}.
1706     *
1707     * @param dataChoice        Either the
1708     *                          {@link ucar.unidata.data.CompositeDataChoice}
1709     *                          representing all time steps or a
1710     *                          {@link ucar.unidata.data.DirectDataChoice}
1711     *                          representing a single time step.
1712     * @param category          Not really used.
1713     * @param dataSelection     Defines any time subsets.
1714     * @param requestProperties extra request properties
1715     *
1716     * @return The image or image sequence data.
1717     *
1718     * @throws RemoteException    Java RMI problem
1719     * @throws VisADException     VisAD problem
1720     */
1721    
1722    protected Data getDataInner(DataChoice dataChoice, DataCategory category,
1723                                DataSelection dataSelection,
1724                                Hashtable requestProperties)
1725            throws VisADException, RemoteException {
1726        Data img = null;
1727        iml = new ArrayList();
1728
1729        if (dataSelection == null) {
1730            return null;
1731        }
1732        setDataSelection(dataSelection);
1733
1734        // assuming the Double.NaN bbox is easy to replace with
1735        // an actual bbox, the getGeoSelection(createIfNeeded=true) should
1736        // probably populate the geoselection with the Double.NaN bbox.
1737        GeoSelection geoSelection = dataSelection.getGeoSelection(true);
1738        if (geoSelection == null) {
1739            return null;
1740        }
1741
1742        GeoLocationInfo selectionBox = geoSelection.getBoundingBox();
1743        if (selectionBox == null) {
1744            logger.info("MCV3088: incoming dataselection has null bounding box, replacing with default! dataChoice: '{}', dataSelection: {}", dataChoice.getName(), dataSelection);
1745            selectionBox = new GeoLocationInfo(Double.NaN, Double.NaN, Double.NaN, Double.NaN);
1746            geoSelection.setBoundingBox(selectionBox);
1747        }
1748
1749        boolean validState = geoSelection.getHasValidState();
1750        if (!validState) {
1751            return null;
1752        }
1753
1754        if (this.lastGeoSelection == null) {
1755            this.lastGeoSelection = geoSelection;
1756        }
1757
1758        this.selectionProps = dataSelection.getProperties();
1759        Enumeration propEnum = this.selectionProps.keys();
1760        while (propEnum.hasMoreElements()) {
1761            String key = propEnum.nextElement().toString();
1762            if (key.compareToIgnoreCase(LATLON_KEY) == 0) {
1763                String val = (String)this.selectionProps.get(key);
1764                if (val.contains("NaN")) {
1765                    return img;
1766                }
1767            }
1768            if (key.compareToIgnoreCase(LINELE_KEY) == 0) {
1769                String val = (String)this.selectionProps.get(key);
1770                if (val.contains("NaN")) {
1771                    return img;
1772                }
1773            }
1774        }
1775
1776        if (this.selectionProps.containsKey("MAG")) {
1777            String str = (String)this.selectionProps.get("MAG");
1778            String[] strs = StringUtil.split(str, " ", 2);
1779            this.lineMag = new Integer(strs[0]).intValue();
1780            this.elementMag = new Integer(strs[1]).intValue();
1781        }
1782        this.choiceName = dataChoice.getName();
1783        if (this.choiceName != null) {
1784            setProperty(PROP_DATACHOICENAME, this.choiceName);
1785        }
1786        try {
1787            img = super.getDataInner(dataChoice, category, dataSelection, requestProperties);
1788        } catch (Exception e) {
1789            String displaySrc = getDisplaySource();
1790            if (displaySrc != null) {
1791                AddeImageDescriptor aid = new AddeImageDescriptor(displaySrc);
1792                dataChoice.setId((Object)aid);
1793                img = super.getDataInner(dataChoice, category, dataSelection, requestProperties);
1794            }
1795        }
1796        return img;
1797    }
1798
1799    /**
1800     * Check if the DataChoice has a BandInfo for its ID
1801     *
1802     * @param dataChoice  choice to check
1803     *
1804     * @return true if the choice ID is a BandInfo
1805     */
1806    
1807    private boolean hasBandInfo(DataChoice dataChoice) {
1808        Object id = dataChoice.getId();
1809        return id instanceof BandInfo;
1810    }
1811
1812    /** _more_ */
1813    AreaDirectory[][] currentDirs;
1814
1815    /**
1816     * Create the  image sequence defined by the given dataChoice.
1817     *
1818     * @param dataChoice     The choice.
1819     * @param subset     any time subsets.
1820     * @return The image sequence.
1821     *
1822     * @throws RemoteException    Java RMI problem
1823     * @throws VisADException     VisAD problem
1824     */
1825    
1826    protected ImageSequence makeImageSequence(DataChoice dataChoice, DataSelection subset)
1827            throws VisADException, RemoteException {
1828
1829//        if (dataChoice.getDataSelection() == null) {
1830//            dataChoice.setDataSelection(subset);
1831//        }
1832        Hashtable subsetProperties = subset.getProperties();
1833        Enumeration propEnum = subsetProperties.keys();
1834        int numLines = 0;
1835        int numEles = 0;
1836        while (propEnum.hasMoreElements()) {
1837            String key = propEnum.nextElement().toString();
1838            if (key.compareToIgnoreCase(SIZE_KEY) == 0) {
1839                String sizeStr = (String)(subsetProperties.get(key));
1840                String[] vals = StringUtil.split(sizeStr, " ", 2);
1841                Integer iVal = new Integer(vals[0]);
1842                numLines = iVal.intValue();
1843                iVal = new Integer(vals[1]);
1844                numEles = iVal.intValue();
1845                break;
1846            }
1847        }
1848
1849        if (sampleMapProjection == null) {
1850            String addeCmdBuff = baseSource;
1851            AreaFile af = null;
1852            try {
1853                af = new AreaFile(addeCmdBuff);
1854            } catch (Exception eOpen) {
1855                logger.error("could not open area file: {}", eOpen);
1856                setInError(true);
1857                throw new BadDataException("Opening area file: " + eOpen.getMessage(), eOpen);
1858            }
1859            try {
1860//                McIDASAreaProjection map = new McIDASAreaProjection(af);
1861                AREACoordinateSystem acs = new AREACoordinateSystem(af);
1862                sampleMapProjection = (MapProjection)acs;
1863//                sampleProjection = map;
1864            } catch (Exception e) {
1865                logger.error("making area projection: {}", e);
1866                setInError(true);
1867                throw new BadDataException("Making area projection: " + e.getMessage(), e);
1868            }
1869        }
1870        AREACoordinateSystem macs = (AREACoordinateSystem)sampleMapProjection;
1871        int[] dirBlk = macs.getDirBlock();
1872        if (numLines == 0) {
1873            double elelin[][] = new double[2][2];
1874            double latlon[][] = new double[2][2];
1875            GeoSelection gs = subset.getGeoSelection();
1876            GeoLocationInfo gli = gs.getBoundingBox();
1877            if ((gli == null) && (lastGeoSelection != null)) {
1878                subset.setGeoSelection(lastGeoSelection);
1879                gs = lastGeoSelection;
1880                gli = gs.getBoundingBox();
1881            }
1882            LatLonPoint llp = gli.getUpperLeft();
1883            latlon[0][0] = llp.getLatitude();
1884            latlon[1][0] = llp.getLongitude();
1885            llp = gli.getLowerRight();
1886            latlon[0][1] = llp.getLatitude();
1887            latlon[1][1] = llp.getLongitude();
1888            elelin = macs.fromReference(latlon);
1889            numLines = (int)(Math.abs(elelin[1][0] - elelin[1][1]))*dirBlk[11];
1890            numEles = (int)(Math.abs(elelin[0][1] - elelin[0][0]))*dirBlk[12];
1891        }
1892
1893        try {
1894            descriptorsToUse = new ArrayList();
1895            if (hasBandInfo(dataChoice)) {
1896                descriptorsToUse = getDescriptors(dataChoice, subset);
1897            } else {
1898                List choices = (dataChoice instanceof CompositeDataChoice)
1899                               ? getChoicesFromSubset(
1900                                   (CompositeDataChoice) dataChoice, subset)
1901                               : Arrays.asList(new DataChoice[] {
1902                                   dataChoice });
1903                for (Iterator iter = choices.iterator(); iter.hasNext(); ) {
1904                    DataChoice          subChoice = (DataChoice) iter.next();
1905                    AddeImageDescriptor aid =
1906                        getDescriptor(subChoice.getId());
1907                    if (aid == null) {
1908                        continue;
1909                    }
1910                    DateTime dttm = aid.getImageTime();
1911                    if ((subset != null) && (dttm != null)) {
1912                        List times = getTimesFromDataSelection(subset,
1913                                         dataChoice);
1914                        if ((times != null) && (times.indexOf(dttm) == -1)) {
1915                            continue;
1916                        }
1917                    }
1918                    descriptorsToUse.add(aid);
1919                }
1920            }
1921
1922            if (descriptorsToUse == null || descriptorsToUse.size() == 0) {
1923                return null;
1924            }
1925            AddeImageInfo biggestPosition = null;
1926            int           pos             = 0;
1927            boolean       anyRelative     = false;
1928            // Find the descriptor with the largest position
1929            for (Iterator iter = descriptorsToUse.iterator(); iter.hasNext(); ) {
1930                AddeImageDescriptor aid = (AddeImageDescriptor) iter.next();
1931                if (aid.getIsRelative()) {
1932                    anyRelative = true;
1933                }
1934                AddeImageInfo       aii = aid.getImageInfo();
1935
1936                // Are we dealing with area files here?
1937                if (aii == null) {
1938                    break;
1939                }
1940
1941                // Check if this is absolute time
1942                if ((aii.getStartDate() != null) || (aii.getEndDate() != null)) {
1943                    biggestPosition = null;
1944                    break;
1945                }
1946                if ((biggestPosition == null) || (Math.abs(aii.getDatasetPosition()) > pos)) {
1947                    pos             = Math.abs(aii.getDatasetPosition());
1948                    biggestPosition = aii;
1949                }
1950            }
1951
1952            if (getCacheDataToDisk() && anyRelative && (biggestPosition != null)) {
1953                biggestPosition.setRequestType(AddeImageInfo.REQ_IMAGEDIR);
1954                AreaDirectoryList adl = new AreaDirectoryList(biggestPosition.getURLString());
1955                biggestPosition.setRequestType(AddeImageInfo.REQ_IMAGEDATA);
1956                currentDirs = adl.getSortedDirs();
1957            } else {
1958                currentDirs = null;
1959            }
1960
1961            int cnt = 1;
1962            DataChoice parent = dataChoice.getParent();
1963            final List<SingleBandedImage> images = new ArrayList<SingleBandedImage>();
1964            MathType rangeType = null;
1965            for (Iterator iter = descriptorsToUse.iterator(); iter.hasNext(); ) {
1966                final AddeImageDescriptor aid = (AddeImageDescriptor) iter.next();
1967                if (currentDirs != null) {
1968                    int idx = Math.abs(aid.getImageInfo().getDatasetPosition());
1969                    if (idx >= currentDirs.length) {
1970                        continue;
1971                    }
1972                }
1973
1974                String label = "";
1975                if (parent != null) {
1976                    label = label + parent.toString() + ' ';
1977                } else {
1978                    DataCategory displayCategory = dataChoice.getDisplayCategory();
1979                    if (displayCategory != null) {
1980                        label = label + displayCategory + ' ';
1981                    }
1982                }
1983                label = label + dataChoice.toString();
1984                final String readLabel = "Time: " + (cnt++) + '/'
1985                    + descriptorsToUse.size() + ' '
1986                    + label;
1987
1988                String src = aid.getSource();
1989                if (!isFromFile(aid)) {
1990                    try {
1991                        src = replaceKey(src, LINELE_KEY, (Object)("1 1"));
1992                        String sizeString = "10 10";
1993                        src = replaceKey(src, SIZE_KEY, (Object)(sizeString));
1994                        String name = dataChoice.getName();
1995                        int idx = name.lastIndexOf('_');
1996                        String unit = name.substring(idx+1);
1997                        if (getKey(src, UNIT_KEY).length() == 0) {
1998                            src = replaceKey(src, UNIT_KEY, (Object)(unit));
1999                        }
2000                        int lSize = numLines;
2001                        int eSize = numEles;
2002
2003                        int magFactor = 1;
2004
2005                        // TJJ Mar 2020 - For derived fields, see if product bands are mixed-resolution bands
2006                        if (isDerived) {
2007                            int derivedBandNum = Integer.parseInt(getKey(src, BAND_KEY));
2008                            magFactor = derivedBandMagFactor.get(derivedBandNum);
2009                        }
2010
2011                        sizeString = lSize / magFactor + " " + eSize / magFactor;
2012                        src = replaceKey(src, SIZE_KEY, (Object) (sizeString));
2013                        src = replaceKey(src, MAG_KEY, (Object) (this.lineMag + " " + this.elementMag));
2014                        aid.setSource(src);
2015                        logger.debug("makeImageSequence src: " + src);
2016                    } catch (Exception exc) {
2017                        logger.error("error trying to adjust AddeImageDescriptor: {}", exc);
2018                        super.makeImageSequence(dataChoice, subset);
2019                    }
2020                }
2021
2022                try {
2023                    SingleBandedImage image = makeImage(aid, rangeType, true, readLabel, subset);
2024                    if (image != null) {
2025                        if(rangeType==null) {
2026                            rangeType = ((FunctionType) image.getType()).getRange();
2027                        }
2028                        synchronized (images) {
2029                            images.add(image);
2030                        }
2031                    }
2032                } catch (VisADException e) {
2033                    logger.error("avoiding visad exception: ",e);
2034                } catch (RemoteException e) {
2035                    logger.error("avoiding remote exception: ", e);
2036                }
2037            }
2038
2039            TreeMap imageMap = new TreeMap();
2040            for (SingleBandedImage image : images) {
2041                imageMap.put(image.getStartTime(), image);
2042            }
2043            List<SingleBandedImage> sortedImages = (List<SingleBandedImage>) new ArrayList(imageMap.values());
2044            if ((sortedImages.size() > 0) && (sortedImages.get(0) instanceof AreaImageFlatField)) {
2045                DataRange[] sampleRanges = null;
2046                Set domainSet = null;
2047                for (SingleBandedImage sbi : sortedImages) {
2048                    AreaImageFlatField aiff = (AreaImageFlatField) sbi;
2049                    sampleRanges = aiff.getRanges(true);
2050                    if (domainSet == null) {
2051                        domainSet = aiff.getDomainSet();
2052                    }
2053                    if ((sampleRanges != null) && (sampleRanges.length > 0)) {
2054                        for (int rangeIdx = 0; rangeIdx < sampleRanges.length; rangeIdx++) {
2055                            DataRange r = sampleRanges[rangeIdx];
2056                            if (Double.isInfinite(r.getMin()) || Double.isInfinite(r.getMax())) {
2057                                sampleRanges = null;
2058                                break;
2059                            }
2060                        }
2061                    }
2062                    if (sampleRanges != null) {
2063                        break;
2064                    }
2065                }
2066
2067                if (sampleRanges != null) {
2068                    for (SingleBandedImage sbi : sortedImages) {
2069                        AreaImageFlatField aiff = (AreaImageFlatField) sbi;
2070                        aiff.setSampleRanges(sampleRanges);
2071                        aiff.setDomainIfNeeded(domainSet);
2072                    }
2073                }
2074            }
2075
2076            SingleBandedImage[] imageArray =
2077                (SingleBandedImage[]) sortedImages.toArray(
2078                    new SingleBandedImage[sortedImages.size()]);
2079            FunctionType imageFunction =
2080                (FunctionType) imageArray[0].getType();
2081            FunctionType ftype = new FunctionType(RealType.Time,
2082                                     imageFunction);
2083            return new ImageSequenceImpl(ftype, imageArray);
2084        } catch (Exception exc) {
2085            throw new ucar.unidata.util.WrapperException(exc);
2086        }
2087    }
2088    
2089    /**
2090     * Create the single image defined by the given 
2091     * {@link ucar.unidata.data.imagery.AddeImageDescriptor AddeImageDescriptor}.
2092     *
2093     * @param aid Holds image directory and location of the desired image.
2094     * @param rangeType {@literal "rangeType"} to use (if non-{@code null}).
2095     * @param fromSequence _more_
2096     * @param readLabel 
2097     * @param subset geographical subsetting info
2098     *
2099     * @return The data.
2100     *
2101     * @throws RemoteException Java RMI problem
2102     * @throws VisADException VisAD problem
2103     */
2104    
2105    private SingleBandedImage makeImage(AddeImageDescriptor aid,
2106                                        MathType rangeType,
2107                                        boolean fromSequence, 
2108                                        String readLabel, DataSelection subset)
2109            throws VisADException, RemoteException {
2110        
2111        if (aid == null) {
2112            return null;
2113        }
2114
2115        logger.trace("incoming src={} DateTime={} readLabel={}", new Object[] { aid.getSource(), aid.getImageTime(), readLabel });
2116        String src = aid.getSource();
2117
2118        Hashtable props = subset.getProperties();
2119        
2120//        String areaDirectoryKey = getKey(src, "POS");
2121        String areaDirectoryKey = null;
2122        if (aid.getIsRelative()) {
2123            areaDirectoryKey = getKey(src, "POS");
2124        } else {
2125            areaDirectoryKey = aid.getImageTime().toString();
2126//            areaDirectoryKey = getKey(src, "TIME");
2127        }
2128
2129        // it only makes sense to set the following properties for things
2130        // coming from an ADDE server
2131        if (!isFromFile(aid)) {
2132            if (props.containsKey("PLACE")) {
2133                src = replaceKey(src, "PLACE", props.get("PLACE"));
2134            }
2135            if (props.containsKey("LATLON")) { 
2136                src = replaceKey(src, "LINELE", "LATLON", props.get("LATLON"));
2137            }
2138            if (props.containsKey("LINELE")) {
2139                src = removeKey(src, "LATLON");
2140                src = replaceKey(src, "LINELE", props.get("LINELE"));
2141            }
2142            if (props.containsKey("MAG")) {
2143                src = replaceKey(src, "MAG", props.get("MAG"));
2144            }
2145        }
2146
2147        aid.setSource(src);
2148
2149        SingleBandedImage result;
2150        result = (SingleBandedImage)getCache(src);
2151        if (result != null) {
2152            setDisplaySource(src, props);
2153            return result;
2154        }
2155
2156        // For now handle non ADDE URLs here
2157        try {
2158            AddeImageInfo aii = aid.getImageInfo();
2159            AreaDirectory areaDir = null;
2160            try {
2161                if (aii != null) {
2162                    logger.trace("imageinfo={}", aii.toString());
2163                    if (currentDirs != null) {
2164                        int pos = Math.abs(aii.getDatasetPosition());
2165                        int band = 0;
2166                        String bandString = aii.getBand();
2167                        if ((bandString != null) && !AddeURL.ALL.equals(bandString)) {
2168                            band = new Integer(bandString).intValue();
2169                        }
2170                        // TODO: even though the band is non-zero we might only 
2171                        // get back one band
2172                        band = 0;
2173                        areaDir = currentDirs[currentDirs.length - pos - 1][band];
2174                    } else {
2175                        // If its absolute time then just use the AD from the descriptor
2176                        if ((aii.getStartDate() != null) || (aii.getEndDate() != null)) {
2177                            areaDir = aid.getDirectory();
2178                        } else {
2179                        }
2180                    }
2181                } else {
2182                    logger.trace("uh oh");
2183                }
2184            } catch (Exception exc) {
2185                LogUtil.printMessage("error looking up area dir");
2186                logger.error("error looking up area dir", exc);
2187                return null;
2188            }
2189
2190            if (areaDir == null) {
2191                areaDir = aid.getDirectory();
2192            }
2193
2194            if (!getCacheDataToDisk()) {
2195                areaDir = null;
2196            }
2197
2198            if (!fromSequence || (aid.getIsRelative() && (currentDirs == null))) {
2199                areaDir = null;
2200            }
2201
2202            if (areaDir != null) {
2203                if (isFromFile(aid)) {
2204                  if (rangeType == null) {
2205                      result = AreaImageFlatField.createImmediate(aid, readLabel);
2206                  } else {
2207                      // Else, pass in the already created range type
2208                      result  = AreaImageFlatField.create(aid, areaDir, rangeType, readLabel);
2209                  }
2210                }
2211
2212            } else {
2213                src = aid.getSource();
2214                try {
2215                    savePlace = this.laLoSel.getPlace();
2216                    saveLat = this.laLoSel.getLatitude();
2217                    saveLon = this.laLoSel.getLongitude();
2218                    saveNumLine = this.laLoSel.getNumLines();
2219                    saveNumEle = this.laLoSel.getNumEles();
2220                    saveLineMag = this.laLoSel.getLineMag();
2221                    saveEleMag = this.laLoSel.getElementMag();
2222                } catch (Exception e) {
2223                    logger.error("error reading from laLoSel", e);
2224                    this.laLoSel.setPlace(savePlace);
2225                    this.laLoSel.setLatitude(saveLat);
2226                    this.laLoSel.setLongitude(saveLon);
2227                    this.laLoSel.setNumLines(saveNumLine);
2228                    this.laLoSel.setNumEles(saveNumEle);
2229                    this.laLoSel.setLineMag(saveLineMag);
2230                    this.laLoSel.setElementMag(saveEleMag);
2231                }
2232
2233                src = replaceKey(src, PLACE_KEY, savePlace);
2234                src = removeKey(src, LINELE_KEY);
2235                if (getKey(src, LATLON_KEY).length() != 0) {
2236                    String latStr = Double.toString(saveLat);
2237                    if (latStr.length() > 8) {
2238                        latStr = latStr.substring(0,7);
2239                    }
2240                    String lonStr = Double.toString(saveLon);
2241                    if (lonStr.length() > 9) {
2242                        lonStr = lonStr.substring(0,8);
2243                    }
2244                    src = replaceKey(src, LATLON_KEY, latStr + ' ' + lonStr);
2245                }
2246                src = replaceKey(src, SIZE_KEY, saveNumLine + ' ' + saveNumEle);
2247                src = replaceKey(src, MAG_KEY, saveLineMag + ' ' + saveEleMag);
2248            }
2249
2250            AreaAdapter aa = new AreaAdapter(src, false);
2251            logger.trace("Getting a new aa={} for src=: {}", aa, src);
2252            areaDir = previewDir;
2253            result = aa.getImage();
2254
2255            putCache(src, result);
2256            aid.setSource(src);
2257            iml.add(aid);
2258            setImageList(iml);
2259            setDisplaySource(src, props);
2260            return result;
2261
2262        } catch (java.io.IOException ioe) {
2263            throw new VisADException("Error creating AreaAdapter", ioe);
2264        }
2265    }
2266    
2267    /**
2268     * Make a parameter name for the BandInfo
2269     *
2270     * @param bi    the BandInfo in question
2271     *
2272     * @return  a name for the parameter
2273     */
2274    
2275    private static String makeBandParam(BandInfo bi) {
2276        return new StringBuilder()
2277            .append(bi.getSensor())
2278            .append("_Band")
2279            .append(bi.getBandNumber())
2280            .append('_')
2281            .append(bi.getPreferredUnit()).toString();
2282    }
2283    
2284    private static String makeBandParam(AddeImageDescriptor descriptor) {
2285        AreaDirectory areaDir = descriptor.getDirectory();
2286        if (areaDir == null) {
2287            throw new NullPointerException("No AREA directory!");
2288        }
2289        return new StringBuilder()
2290            .append(areaDir.getSensorID())
2291            .append("_Band")
2292            .append(areaDir.getBands()[0])
2293            .append('_')
2294            .append(areaDir.getCalibrationType()).toString();
2295    }
2296
2297    /**
2298     * Get a list of descriptors from the choice and subset
2299     *
2300     * @param dataChoice  Data choice
2301     * @param subset  subsetting info
2302     *
2303     * @return  list of descriptors matching the selection
2304     */
2305    
2306    public List getDescriptors(DataChoice dataChoice, DataSelection subset) {
2307//        logger.trace("choice={} subset props={} geo={}", new Object[] { dataChoice, subset.getProperties(), subset.getGeoSelection() });
2308        int linRes = this.lineResolution;
2309        int eleRes = this.elementResolution;
2310        int newLinRes = linRes;
2311        int newEleRes = eleRes;
2312//        List<TwoFacedObject> times = getTimesFromDataSelection(subset, dataChoice);
2313        List times = getTimesFromDataSelection(subset, dataChoice);
2314        boolean usingTimeDriver = ((subset != null) && (subset.getTimeDriverTimes() != null));
2315        if (usingTimeDriver) {
2316            times = subset.getTimeDriverTimes();
2317        }
2318
2319//        if (dataChoice.getDataSelection() == null) {
2320//            logger.trace("setting datasel!");
2321//            dataChoice.setDataSelection(subset);
2322//        }
2323        if ((times == null) || times.isEmpty()) {
2324            times = imageTimes;
2325        }
2326//        List<AddeImageDescriptor> descriptors = new ArrayList<AddeImageDescriptor>(times.size());
2327        List descriptors = new ArrayList();
2328
2329        if (usingTimeDriver) {
2330            if (imageList.isEmpty()) {
2331                return imageList;
2332            }
2333            AddeImageDescriptor aid = getDescriptor(imageList.get(0));
2334            if (aid.getImageInfo() != null) {
2335                try {
2336                    AddeImageInfo aii =
2337                        (AddeImageInfo) aid.getImageInfo().clone();
2338                    // set the start and end dates
2339                    Collections.sort(times);
2340                    DateTime start = (DateTime) times.get(0);
2341                    DateTime end   = (DateTime) times.get(times.size() - 1);
2342                    // In ADDE, you can't specify something like DAY=2011256 2011257 TIME=23:45:00 01:45:00
2343                    // and expect that to be 2011256/23:45 to 2011257 01:45.  Time ranges are on a per day
2344                    // basis.  So, we see if the starting time is a different day than the ending day and if so,
2345                    // we set the start time to be 00Z on the first day an 23:59Z on the end day.
2346                    // Even worse is that for archive datasets, you can't span multiple days.  So make separate
2347                    // requests for each day.
2348                    String       startDay = UtcDate.getYMD(start);
2349                    String       endDay   = UtcDate.getYMD(end);
2350                    List<String> days     = new ArrayList<>(times.size());
2351                    if (!startDay.equals(endDay)) {
2352                        days = getUniqueDayStrings(times);
2353                    } else {
2354                        days.add(startDay);
2355                    }
2356                    Map<DateTime, AreaDirectory> dateDir = new HashMap<>(days.size());
2357                    List<DateTime> dirTimes = new ArrayList<>(days.size() * 10);
2358                    for (String day : days) {
2359                        startDay = day + " 00:00:00";
2360                        endDay   = day + " 23:59:59";
2361                        start = DateTime.createDateTime(startDay,
2362                            DateTime.DEFAULT_TIME_FORMAT);
2363                        end = UtcDate.createDateTime(endDay,
2364                            DateTime.DEFAULT_TIME_FORMAT);
2365                        aii.setStartDate(new Date((long) (start
2366                            .getValue(CommonUnit.secondsSinceTheEpoch) * 1000)));
2367                        aii.setEndDate(new Date((long) (end
2368                            .getValue(CommonUnit.secondsSinceTheEpoch) * 1000)));
2369                        // make the request for the times (AreaDirectoryList)
2370                        aii.setRequestType(AddeURL.REQ_IMAGEDIR);
2371                        AreaDirectoryList ad;
2372                        try {  // we may be asking for a date that doesn't exist
2373                            ad = new AreaDirectoryList(aii.getURLString());
2374                        } catch (AreaFileException afe) {
2375                            // If there's an error, we just ignore it.  In the
2376                            // end, the descriptor list will be empty if there is no
2377                            // data for any of the days.
2378                            continue;
2379
2380                            // TODO: This is a hack because different servers return different
2381                            // messages.  AREA and GINI servers seem to have "no images" in the
2382                            // exception message when there are no images.
2383                            //String message = afe.getMessage().toLowerCase();
2384                            //if (message.indexOf("no images") >= 0 ||
2385                            //    message.indexOf("error generating list of files") >= 0) {
2386                            //    continue;
2387                            //} else {
2388                            //    throw afe;
2389                            //}
2390
2391                        }
2392                        AreaDirectory[][] dirs = ad.getSortedDirs();
2393                        for (int d = 0; d < dirs.length; d++) {
2394                            AreaDirectory dir = dirs[d][0];
2395                            DateTime dirTime =
2396                                new DateTime(dir.getNominalTime());
2397                            dateDir.put(dirTime, dir);
2398                            dirTimes.add(dirTime);
2399                        }
2400                    }
2401                    List<DateTime> matchedTimes = selectTimesFromList(subset,
2402                        dirTimes, times);
2403                    for (DateTime dirTime : matchedTimes) {
2404                        AreaDirectory dir = dateDir.get(dirTime);
2405                        // shouldn't happen, but what the hey
2406                        if (dir == null) {
2407                            continue;
2408                        }
2409                        AddeImageInfo newaii =
2410                            (AddeImageInfo) aid.getImageInfo().clone();
2411                        newaii.setRequestType(aii.REQ_IMAGEDATA);
2412                        newaii.setStartDate(dir.getNominalTime());
2413                        newaii.setEndDate(dir.getNominalTime());
2414                        setBandInfo(dataChoice, newaii);
2415                        AddeImageDescriptor newaid =
2416                            new AddeImageDescriptor(dir,
2417                                newaii.getURLString(), newaii);
2418                        newaid.setIsRelative(false);
2419                        descriptors.add(newaid);
2420                    }
2421                } catch (CloneNotSupportedException cnse) {
2422                    logger.error("Unable to clone AddeImageInfo (aii)", cnse);
2423                } catch (VisADException vader) {
2424                    logger.error("Unable to get date values", vader);
2425                } catch (AreaFileException afe) {
2426                    logger.error("Unable to make ADDE request", afe);
2427                } catch (Exception excp) {
2428                    logger.error("Exception occurred while handling time driver", excp);
2429                }
2430                // we do this so save data local will work.  However, if
2431                // this then gets set to be the time driver, it would not
2432                // necessarily be correct
2433                imageList = descriptors;
2434                return descriptors;
2435            } else if (imageList != null) {
2436                return imageList;
2437            }
2438        }
2439
2440        int choiceBandNum = ((BandInfo) dataChoice.getId()).getBandNumber();
2441        int choiceSensorId = ((BandInfo) dataChoice.getId()).getSensor();
2442        String choicePrefUnit = ((BandInfo) dataChoice.getId()).getPreferredUnit();
2443        for (Iterator iter = times.iterator(); iter.hasNext(); ) {
2444            Object time  = iter.next();
2445            AddeImageDescriptor found = null;
2446            AddeImageDescriptor foundTimeMatch = null;
2447            if (saveImageList.isEmpty()) {
2448                saveImageList = getImageList();
2449            }
2450            for (Iterator iter2 = saveImageList.iterator(); iter2.hasNext(); ) {
2451                AddeImageDescriptor aid = getDescriptor(iter2.next());
2452                if (aid != null) {
2453                    if (aid.getIsRelative()) {
2454                        Object id;
2455                        if (time instanceof TwoFacedObject) {
2456                            id = ((TwoFacedObject)time).getId();
2457                        } else {
2458                            id = time;
2459                        }
2460                        if ((id instanceof Integer) && ((Integer)id).intValue() == aid.getRelativeIndex()) {
2461                            found = aid;
2462                            break;
2463                        }
2464                    } else {
2465                        int aidBand = aid.getDirectory().getBands()[0];
2466                        int aidSensorId = aid.getDirectory().getSensorID();
2467                        String calType = aid.getDirectory().getCalibrationType();
2468                        if (foundTimeMatch == null && aid.getImageTime().equals(time)) {
2469                            logger.trace("found time match {}", time);
2470                            foundTimeMatch = aid;
2471                        }
2472                        if (aid.getImageTime().equals(time) && choiceBandNum == aidBand && choiceSensorId == aidSensorId && choicePrefUnit.equals(calType)) {
2473                            // the problem is here!
2474                            logger.trace("found aid={} src={}", makeBandParam(aid), aid.getSource());
2475                            logger.trace("target info: param={}", dataChoice.getName());
2476                            found = aid;
2477                            break;
2478                        }
2479                    }
2480                }
2481            }
2482
2483            if (found == null && foundTimeMatch != null) {
2484                logger.trace("good enough!?");
2485                found = foundTimeMatch;
2486            }
2487            
2488            if (found != null) {
2489                try {
2490                    AddeImageDescriptor desc = new AddeImageDescriptor(found);
2491                    // Sometimes we might have a null imageinfo
2492                    if (desc.getImageInfo() != null) {
2493                        AddeImageInfo aii =
2494                            (AddeImageInfo) desc.getImageInfo().clone();
2495                        BandInfo bi = (BandInfo) dataChoice.getId();
2496                        List<BandInfo> bandInfos =
2497                            (List<BandInfo>) getProperty(PROP_BANDINFO, (Object) null);
2498                        boolean hasBand = true;
2499                        // If this data source has been changed after we have create a display 
2500                        // then the possibility exists that the bandinfo contained by the incoming
2501                        // data choice might not be valid. If it isn't then default to the first 
2502                        // one in the list
2503                        if (bandInfos != null) {
2504                            hasBand = bandInfos.contains(bi);
2505                            if(!hasBand) {
2506                            }
2507                            if(!hasBand && bandInfos.size() > 0) {
2508                                bi = bandInfos.get(0);
2509                            } else {
2510                                //Not sure what to do here.
2511                            }
2512                        }
2513                        aii.setBand("" + bi.getBandNumber());
2514                        aii.setPlaceValue("ULEFT");
2515
2516                        try {
2517                            AddeImageDescriptor newAid = new AddeImageDescriptor(aii.getURLString());
2518                            AreaDirectory newAd = newAid.getDirectory();
2519                            newLinRes = newAd.getValue(AreaFile.AD_LINERES);
2520                            newEleRes = newAd.getValue(AreaFile.AD_ELEMRES);
2521                        } catch (Exception e) {
2522                            logger.error("resetting resolution", e);
2523                        }
2524
2525                        double[][] projCoords = new double[2][2];
2526                        try {
2527                            AreaDirectory ad = desc.getDirectory();
2528                            double lin = (double) ad.getValue(AreaFile.AD_STLINE);
2529                            double ele = (double) ad.getValue(AreaFile.AD_STELEM);
2530                            aii.setLocateKey("LINELE");
2531                            aii.setLocateValue((int)lin + " " + (int)ele);
2532                            projCoords[0][0] = lin;
2533                            projCoords[1][0] = ele;
2534                            lin += (double)ad.getValue(AreaFile.AD_NUMLINES);
2535                            ele += (double)ad.getValue(AreaFile.AD_NUMELEMS);
2536                            projCoords[0][1] = lin;
2537                            projCoords[1][1] = ele;
2538                        } catch (Exception e) {
2539                            logger.error("problem with adjusting projCoords?", e);
2540                            return descriptors;
2541                        }
2542                        int lins = Math.abs((int)(projCoords[1][1] - projCoords[1][0]));
2543                        int eles = Math.abs((int)(projCoords[0][1] - projCoords[0][0]));
2544                        lins = lins*linRes/newLinRes;
2545                        if (this.lineMag > 0) {
2546                            lins *= this.lineMag;
2547                        } else {
2548                            lins /= -this.lineMag;
2549                        }
2550
2551                        eles = eles*eleRes/newEleRes;
2552
2553                        if (elementMag > 0) {
2554                            eles *= elementMag;
2555                        } else {
2556                            eles /= -elementMag;
2557                        }
2558
2559                        aii.setLines(lins);
2560                        aii.setElements(eles);
2561                        desc.setImageInfo(aii);
2562                        desc.setSource(aii.getURLString());
2563                    }
2564                    descriptors.add(desc);
2565                } catch (CloneNotSupportedException cnse) {}
2566            }
2567        }
2568        return descriptors;
2569    }
2570
2571    /**
2572     * Get the subset of the composite based on the selection
2573     *
2574     * @param choice  composite choice
2575     * @param subset  time selection
2576     *
2577     * @return subset list
2578     */
2579    
2580    private List getChoicesFromSubset(CompositeDataChoice choice,
2581                                      DataSelection subset) {
2582        List choices = choice.getDataChoices();
2583        if (subset == null) {
2584            return choices;
2585        }
2586        List times = subset.getTimes();
2587        if (times == null) {
2588            return choices;
2589        }
2590        times = TwoFacedObject.getIdList(times);
2591        List   subChoices = new ArrayList();
2592        Object firstTime  = times.get(0);
2593        if (firstTime instanceof Integer) {
2594            for (Iterator iter = times.iterator(); iter.hasNext(); ) {
2595                subChoices.add(
2596                    choices.get(((Integer) iter.next()).intValue()));
2597            }
2598        } else {  // TODO: what if they are DateTimes?
2599            subChoices.addAll(choices);
2600        }
2601        return subChoices;
2602    }
2603
2604    private List<AreaDirectory> getPreviewDirectories(final AddeImageDescriptor imageDescriptor) {
2605        List<AreaDirectory> directories = new ArrayList<AreaDirectory>(imageTimes.size());
2606        
2607        return directories;
2608    }
2609    
2610//    private AreaDirectory getPreviewDirectory(AddeImageDescriptor aid) {
2611//        AreaDirectory directory = aid.getDirectory();
2612//        AddeImageDescriptor descriptor = null;
2613//        int times = imageTimes.size();
2614//        if (times == 1) {
2615//            logger.trace("only looking for a single time; returning AreaDirectory grabbed from incoming AddeImageDescriptor. directory={}", directory);
2616//            return directory;
2617//        }
2618//        String src = aid.getSource();
2619//        logger.trace("URL for incoming AddeImageDescriptor: {}", src);
2620//        src = removeKey(src, LATLON_KEY);
2621//        src = removeKey(src, LINELE_KEY);
2622//        src = removeKey(src, PLACE_KEY);
2623//        src = removeKey(src, SIZE_KEY);
2624//        src = removeKey(src, UNIT_KEY);
2625//        src = removeKey(src, MAG_KEY);
2626//        src = removeKey(src, SPAC_KEY);
2627//        src = removeKey(src, NAV_KEY);
2628//        src = removeKey(src, AUX_KEY);
2629//        src = removeKey(src, DOC_KEY);
2630//
2631//        int maxLine = 0;
2632//        int maxEle = 0;
2633//        int imageSize = 0;
2634//        src = src.replace("imagedata", "imagedir");
2635//        boolean isRelative = aid.getIsRelative();
2636//        for (int i=0; i<times; i++) {
2637//            if (isRelative) {
2638//                src = replaceKey(src, "POS", new Integer(i).toString());
2639//            } else {
2640//                DateTime dt = (DateTime)imageTimes.get(i);
2641//                String timeStr = dt.timeString();
2642//                timeStr = timeStr.replace("Z", " ");
2643//                src = removeKey(src, "POS");
2644//                src = replaceKey(src, "TIME", timeStr + timeStr + "I");
2645//            }
2646//            try {
2647//                logger.trace("attempting to create AreaDirectoryList using src={}", src);
2648//                AreaDirectoryList dirList = new AreaDirectoryList(src);
2649//                List ad = dirList.getDirs();
2650//                AreaDirectory areaDir = (AreaDirectory)ad.get(0);
2651//                logger.trace("created AreaDirectory: {}", areaDir);
2652//                int lines = areaDir.getLines();
2653//                int eles =  areaDir.getElements();
2654//                if (imageSize < lines*eles) {
2655//                    imageSize = lines * eles;
2656//                    maxLine = lines;
2657//                    maxEle = eles;
2658//                    directory = areaDir;
2659//                    descriptor = new AddeImageDescriptor(src);
2660//                }
2661//            } catch (Exception e) {
2662//                logger.error("problem when dealing with AREA directory", e);
2663//            }
2664//        }
2665//        logger.trace("returning AreaDirectory: {}", directory);
2666//        logger.trace("could return AddeImageDescriptor:\nisRelative={}\nrelativeIndex={}\ntime={}\ndirectory={}\n", new Object[] { descriptor.getIsRelative(), descriptor.getRelativeIndex(), descriptor.getImageTime(), descriptor.getDirectory()});
2667//        return directory;
2668//    }
2669
2670    private AddeImageDescriptor getPreviewDirectory(AddeImageDescriptor aid, DataChoice dataChoice) {
2671
2672        // Used to detect domain shifts for sectors like ABI MESO
2673        float domainCenterLat = -1.0f;
2674        float domainCenterLon = -1.0f;
2675
2676        AreaDirectory directory = aid.getDirectory();
2677        AddeImageDescriptor descriptor = null;
2678        int times = imageTimes.size();
2679        if (times == 1) {
2680            logger.trace("only looking for a single time; returning AreaDirectory grabbed from incoming AddeImageDescriptor. directory={}", directory);
2681//            return directory;
2682            return aid;
2683        }
2684
2685        String src = aid.getSource();
2686        logger.trace("URL for incoming AddeImageDescriptor: {}", src);
2687
2688        src = removeKey(src, LATLON_KEY);
2689        src = removeKey(src, LINELE_KEY);
2690        src = removeKey(src, PLACE_KEY);
2691        src = removeKey(src, SIZE_KEY);
2692        src = removeKey(src, UNIT_KEY);
2693        src = removeKey(src, MAG_KEY);
2694        src = removeKey(src, SPAC_KEY);
2695        src = removeKey(src, NAV_KEY);
2696        src = removeKey(src, AUX_KEY);
2697        src = removeKey(src, DOC_KEY);
2698
2699        int maxLine = 0;
2700        int maxEle = 0;
2701        int imageSize = 0;
2702        int prevSize = 0;
2703        src = src.replace("imagedata", "imagedir");
2704        boolean isRelative = aid.getIsRelative();
2705
2706        // Note the index if we detect a domain shift, we will cut the loop off there
2707        int domainShiftIndex = 0;
2708
2709        List<String> previewUrls = new ArrayList<String>(times);
2710
2711        if (isRelative) {
2712                
2713                // TJJ Mar 2015 - (1891, R14)
2714                // Iterate through list to determine relative position different
2715                // if the objects are VisAD DateTime instead of TwoFacedObject
2716                
2717                boolean isTwoFaced = false;
2718                List<?> tmpList = getAllDateTimes();
2719                if ((tmpList != null) && (! tmpList.isEmpty())) {
2720                        Object firstItem = tmpList.get(0);
2721                        if (firstItem instanceof TwoFacedObject) isTwoFaced = true;
2722                }
2723                
2724                int maxIndex;
2725                        if (isTwoFaced) {
2726                                maxIndex = Integer.MIN_VALUE;
2727                                for (TwoFacedObject tfo : (List<TwoFacedObject>) getAllDateTimes()) {
2728                                        int relativeIndex = ((Integer) tfo.getId()).intValue();
2729                                        if (relativeIndex > maxIndex) {
2730                                                maxIndex = relativeIndex;
2731                                        }
2732                                }
2733                        } else {
2734                                maxIndex = Integer.MIN_VALUE;
2735                                int relativeIndex = 0;
2736                                double maxTime = Double.MIN_VALUE;
2737                                for (DateTime dt : (List<DateTime>) getAllDateTimes()) {
2738                                        double d = dt.getValue();
2739                                        if (d > maxTime) {
2740                                                maxIndex = relativeIndex;
2741                                                maxTime = d;
2742                                        }
2743                                        relativeIndex++;
2744                                }
2745                        }
2746            
2747//            TwoFacedObject tfo = (TwoFacedObject)this.getAllDateTimes().get(0);
2748            // negate maxIndex so we can get things like POS=0 or POS=-4
2749            maxIndex = 0 - maxIndex;
2750            logger.trace("using maxIndex={}", maxIndex);
2751            src = replaceKey(src, "POS", Integer.toString(maxIndex));
2752            previewUrls.add(src);
2753            
2754        } else {
2755            for (int i = 0; i < times; i++) {
2756                DateTime dt = (DateTime)imageTimes.get(i);
2757                String timeStr = dt.timeString();
2758                timeStr = timeStr.replace("Z", " ");
2759                src = removeKey(src, "POS");
2760                src = replaceKey(src, "TIME", timeStr + timeStr + 'I');
2761                logger.trace("using time value: ", timeStr + timeStr + 'I');
2762                logger.trace("added absolute time preview url: {}", src);
2763                previewUrls.add(src);
2764            }
2765        }
2766        
2767        if (! isRelative && (previewUrls.size() == 1)) {
2768            logger.trace("only a single previewUrl; returning '{}'", previewUrls.get(0));
2769            return new AddeImageDescriptor(previewUrls.get(0));
2770        } else {
2771            logger.trace("preparing to examine previewUrls={}", previewUrls);
2772        }
2773        
2774        // TJJ Aug 2020
2775        // Look for GEO-based domain shifts (e.g. MESO sector moves mid-loop)
2776        // The first image checked will initialize domain center lat.  After
2777        // that, anything different is a domain shift
2778
2779        if (isABISensor) {
2780
2781            int urlIdx = 0;
2782            for (String urlStr : previewUrls) {
2783
2784                AddeImageDescriptor tmpAID = new AddeImageDescriptor(urlStr);
2785                AreaDirectory tmpAD = tmpAID.getDirectory();
2786
2787                if (domainCenterLat == -1.0f) {
2788                    domainCenterLat = (float) tmpAD.getCenterLatitude();
2789                } else {
2790                    if ((float) tmpAD.getCenterLatitude() != domainCenterLat) {
2791                        domainCenterLat = (float) tmpAD.getCenterLatitude();
2792                        logger.info("Domain shift, set LAT to: " + domainCenterLat);
2793                        domainShiftDetected = true;
2794                        domainShiftIndex = urlIdx;
2795                    }
2796                }
2797                // The first image checked will initialize domain center lon
2798                if (domainCenterLon == -1.0f) {
2799                    domainCenterLon = (float) tmpAD.getCenterLongitude();
2800                } else {
2801                    if ((float) tmpAD.getCenterLongitude() != domainCenterLon) {
2802                        domainCenterLon = (float) tmpAD.getCenterLongitude();
2803                        logger.info("Domain shift, set LON to: " + domainCenterLon);
2804                        domainShiftDetected = true;
2805                        domainShiftIndex = urlIdx;
2806                    }
2807                }
2808
2809                urlIdx++;
2810
2811            }
2812
2813        }
2814
2815        try {
2816            
2817            // TJJ Nov 2017
2818            // As an interim speedup, for Inq #2496, we are going to binary search the list
2819            // intstead of iterating through every single URL to make large loop times 
2820            // (e.g. 50 GOES-16 MESO images) more tolerable.
2821            
2822            if (isGeoSensor && (previewUrls.size() > 1)) {
2823
2824                int loIdx = 0;
2825                int hiIdx = previewUrls.size() - 1;
2826                boolean directionInc = true;
2827                boolean sizeMatch = false;
2828                int iterations = 0;
2829
2830                while (! sizeMatch) {
2831
2832                    String previewUrl = null;
2833                    if (directionInc) {
2834                        previewUrl = previewUrls.get(loIdx);
2835                        loIdx++;
2836                        directionInc = false;
2837                    } else {
2838                        previewUrl = previewUrls.get(hiIdx);
2839                        hiIdx--;
2840                        directionInc = true;
2841                    }
2842
2843                    // Sanity check - if we get to the middle, we just quit and use what we found
2844                    if ((loIdx > hiIdx) && (! isRelative)) break;
2845
2846                    logger.trace("attempting to create AreaDirectoryList using previewUrl={}", previewUrl);
2847                    AreaDirectoryList directoryList = new AreaDirectoryList(previewUrl);
2848                    logger.trace("created directoryList! size={}\n{}", directoryList.getDirs().size(), directoryList);
2849                    List<AreaDirectory> areaDirectories = (List<AreaDirectory>) directoryList.getDirs();
2850
2851                    for (int i = 0; i < areaDirectories.size(); i++) {
2852                        iterations++;
2853                        AreaDirectory areaDirectory = areaDirectories.get(i);
2854                        String pos = Integer.toString(0 - i);
2855                        int lines = areaDirectory.getLines();
2856                        int elements = areaDirectory.getElements();
2857                        int currentDimensions = lines * elements;
2858                        logger.trace("image pos={} lines={} elements={} lat={} lon={} startTime='{}' nominalTime='{}' currentDimensions={} areaDirectory={}", new Object[] { pos, lines, elements, areaDirectory.getCenterLatitude(), areaDirectory.getCenterLongitude(), areaDirectory.getStartTime(), areaDirectory.getNominalTime(), currentDimensions, areaDirectory });
2859                        String key = null;
2860                        if (isRelative) {
2861                            key = pos;
2862                        } else {
2863                            try {
2864                                DateTime reformatted = new DateTime(areaDirectory.getNominalTime());
2865                                key = reformatted.toString();
2866                            } catch (VisADException e) {
2867                                logger.error("could not reformat time string='"+areaDirectory.getNominalTime().toString()+"'", e);
2868                                key = areaDirectory.getNominalTime().toString();
2869                            }
2870                        }
2871                        requestIdToDirectory.put(key, areaDirectory);
2872
2873                        if (imageSize < currentDimensions) {
2874
2875                            imageSize = currentDimensions;
2876                            maxLine = lines;
2877                            maxEle = elements;
2878                            directory = areaDirectory;
2879                            // TODO(jon): should be grabbing coord sys from chooser setting (HOW TO DO THAT!?)
2880                            String latlonString = areaDirectory.getCenterLatitude() + " " + areaDirectory.getCenterLongitude() + " E";
2881                            String largestPreviewUrl = previewUrl.replace("imagedir", "imagedata");
2882                            largestPreviewUrl = replaceKey(largestPreviewUrl, "PLACE", "CENTER");
2883                            largestPreviewUrl = replaceKey(largestPreviewUrl, "LATLON", latlonString);
2884                            Hashtable dataSourceProperties = this.getProperties();
2885                            if (dataSourceProperties.containsKey("navigation")) {
2886                                largestPreviewUrl = replaceKey(largestPreviewUrl, "NAV", dataSourceProperties.get("navigation"));
2887                            }
2888
2889                            if (isRelative) {
2890                                largestPreviewUrl = replaceKey(largestPreviewUrl, "POS", pos);
2891                            } else {
2892                                logger.trace("need to set DAY and TIME keywords for absolute times!");
2893                            }
2894
2895                            logger.trace("found new max size! old={} new={} url={}", new Object[] { imageSize, currentDimensions, largestPreviewUrl });
2896                            descriptor = new AddeImageDescriptor(areaDirectory, largestPreviewUrl);
2897
2898                        }
2899
2900                        if (prevSize == currentDimensions) {
2901                            logger.debug("Exiting Preview URL loop after " + iterations + " iterations...");
2902                            sizeMatch = true;
2903                        }
2904                        prevSize = currentDimensions;
2905                    }
2906                }
2907            } else {
2908                
2909                // Old way - go through all preview URLs looking for largest geographic coverage
2910
2911                for (String previewUrl : previewUrls) {
2912
2913                    logger.trace("attempting to create AreaDirectoryList using previewUrl={}", previewUrl);
2914                    AreaDirectoryList directoryList = new AreaDirectoryList(previewUrl);
2915                    logger.trace("created directoryList! size={}\n{}", directoryList.getDirs().size(), directoryList);
2916                    List<AreaDirectory> areaDirectories = (List<AreaDirectory>) directoryList.getDirs();
2917
2918                    for (int i = 0; i < areaDirectories.size(); i++) {
2919
2920                        AreaDirectory areaDirectory = areaDirectories.get(i);
2921                        String pos = Integer.toString(0 - i);
2922                        int lines = areaDirectory.getLines();
2923                        int elements = areaDirectory.getElements();
2924                        int currentDimensions = lines * elements;
2925                        logger.trace("image pos={} lines={} elements={} lat={} lon={} startTime='{}' nominalTime='{}' currentDimensions={} areaDirectory={}", new Object[] { pos, lines, elements, areaDirectory.getCenterLatitude(), areaDirectory.getCenterLongitude(), areaDirectory.getStartTime(), areaDirectory.getNominalTime(), currentDimensions, areaDirectory });
2926                        String key = null;
2927                        if (isRelative) {
2928                            key = pos;
2929                        } else {
2930                            try {
2931                                DateTime reformatted = new DateTime(areaDirectory.getNominalTime());
2932                                key = reformatted.toString();
2933                            } catch (VisADException e) {
2934                                logger.error("could not reformat time string='"+areaDirectory.getNominalTime().toString()+"'", e);
2935                                key = areaDirectory.getNominalTime().toString();
2936                            }
2937                        }
2938                        requestIdToDirectory.put(key, areaDirectory);
2939                        if (imageSize < currentDimensions) {
2940
2941                            imageSize = currentDimensions;
2942                            maxLine = lines;
2943                            maxEle = elements;
2944                            directory = areaDirectory;
2945                            // TODO(jon): should be grabbing coord sys from chooser setting (HOW TO DO THAT!?)
2946                            String latlonString = areaDirectory.getCenterLatitude() + " " + areaDirectory.getCenterLongitude() + " E";
2947                            String largestPreviewUrl = previewUrl.replace("imagedir", "imagedata");
2948                            largestPreviewUrl = replaceKey(largestPreviewUrl, "PLACE", "CENTER");
2949                            largestPreviewUrl = replaceKey(largestPreviewUrl, "LATLON", latlonString);
2950                            Hashtable dataSourceProperties = this.getProperties();
2951                            if (dataSourceProperties.containsKey("navigation")) {
2952                                largestPreviewUrl = replaceKey(largestPreviewUrl, "NAV", dataSourceProperties.get("navigation"));
2953                            }
2954
2955                            if (isRelative) {
2956                                largestPreviewUrl = replaceKey(largestPreviewUrl, "POS", pos);
2957                            } else {
2958                                logger.trace("need to set DAY and TIME keywords for absolute times!");
2959                            }
2960
2961                            logger.trace("found new max size! old={} new={} url={}", new Object[] { imageSize, currentDimensions, largestPreviewUrl });
2962                            descriptor = new AddeImageDescriptor(areaDirectory, largestPreviewUrl);
2963
2964                        }
2965                    }
2966                }
2967            }
2968        } catch (AreaFileException areaException) {
2969            logger.error("problem when dealing with AREA directory", areaException);
2970        }
2971//        for (int i = 0; i < times; i++) {
2972//            if (isRelative) {
2973//                src = replaceKey(src, "POS", new Integer(i).toString());
2974//            } else {
2975//                DateTime dt = (DateTime)imageTimes.get(i);
2976//                String timeStr = dt.timeString();
2977//                timeStr = timeStr.replace("Z", " ");
2978//                src = removeKey(src, "POS");
2979//                src = replaceKey(src, "TIME", timeStr + timeStr + "I");
2980//            }
2981//            // don't forget to NOT negate POS=0
2982//            try {
2983//                logger.trace("attempting to create AreaDirectoryList using src={}", src);
2984//                AreaDirectoryList dirList = new AreaDirectoryList(src);
2985//                List ad = dirList.getDirs();
2986//                AreaDirectory areaDir = (AreaDirectory)ad.get(0);
2987//                logger.trace("created AreaDirectory: {}", areaDir);
2988//                int lines = areaDir.getLines();
2989//                int eles =  areaDir.getElements();
2990//                logger.trace("image lines={} eles={} for src={}", new Object[] { lines, eles, src });
2991//                if (imageSize < lines*eles) {
2992//                    logger.trace("found new max size! old={} new={}", imageSize, lines*eles);
2993//                    imageSize = lines * eles;
2994//                    maxLine = lines;
2995//                    maxEle = eles;
2996//                    directory = areaDir;
2997//                    descriptor = new AddeImageDescriptor(src);
2998//                }
2999//            } catch (Exception e) {
3000//                logger.error("problem when dealing with AREA directory", e);
3001//            }
3002//        }
3003//        
3004
3005        // If domain shift was detected, keep only the pre-shift times for derived data
3006        logger.info("domainShiftIndex: " + domainShiftIndex);
3007
3008        logger.trace("returning AreaDirectory: {}", directory);
3009//        logger.trace("could return AddeImageDescriptor:\nisRelative={}\nrelativeIndex={}\ntime={}\ndirectory={}\n", new Object[] { descriptor.getIsRelative(), descriptor.getRelativeIndex(), descriptor.getImageTime(), descriptor.getDirectory()});
3010        return descriptor;
3011    }
3012
3013    private String getServer(String urlString) {
3014        int ix = urlString.indexOf("//") + 2;
3015        String temp = urlString.substring(ix);
3016        ix = temp.indexOf("/");
3017        String retStr = temp.substring(0, ix);
3018        return retStr;
3019    }
3020
3021    public void setDisplaySource(String src, Hashtable props) {
3022         if (!props.isEmpty()) {
3023             Enumeration propEnum = props.keys();
3024             for (int i=0; propEnum.hasMoreElements(); i++) {
3025                 String key = propEnum.nextElement().toString();
3026                 Object val = props.get(key);
3027                 if (getKey(src, key).length() != 0) {
3028                     src = replaceKey(src, key, val);
3029                 }
3030             }
3031         }
3032         this.displaySource = src;
3033         String unit = getKey(src, UNIT_KEY);
3034         if (unit.length() != 0) {
3035             sourceProps.put(UNIT_KEY.toUpperCase(), unit);
3036         }
3037    }
3038
3039    public String getDisplaySource() {
3040        return this.displaySource;
3041    }
3042
3043
3044    private float[] getLineEleResolution(AreaDirectory ad) {
3045        logger.trace("ad: {} sensor: {}", ad, ad.getSensorID());
3046        float[] res = {(float)1.0, (float)1.0};
3047        int sensor = ad.getSensorID();
3048        List lines = null;
3049        try {
3050            String buff = getUrl();
3051
3052            lines = readTextLines(buff);
3053            if (lines == null) {
3054                return res;
3055            }
3056
3057            int gotit = -1;
3058            String[] cards = StringUtil.listToStringArray(lines);
3059            logger.trace("cards: {}", cards);
3060
3061            for (int i = 0; i < cards.length; i++) {
3062                if ( ! cards[i].startsWith("Sat ")) continue;
3063                StringTokenizer st = new StringTokenizer(cards[i]," ");
3064                String temp = st.nextToken();  // throw away the key
3065                int m = st.countTokens();
3066                for (int k = 0; k < m; k++) {
3067                    int ss = Integer.parseInt(st.nextToken().trim());
3068                    if (ss == sensor) {
3069                        gotit = i;
3070                        break;
3071                    }
3072                }
3073
3074                if (gotit != -1) {
3075                    break;
3076                }
3077            }
3078
3079            if (gotit == -1) {
3080                return res;
3081            }
3082
3083            int gotSrc = -1;
3084            for (int i = gotit; i < cards.length; i++) {
3085                if (cards[i].startsWith("EndSat")) {
3086                    return res;
3087                }
3088                if (!cards[i].startsWith("B") ) {
3089                    continue;
3090                }
3091                StringTokenizer tok = new StringTokenizer(cards[i]);
3092                String str = tok.nextToken();
3093                str = tok.nextToken();
3094                Float flt = new Float(str);
3095                res[0] = flt.floatValue();
3096                str = tok.nextToken();
3097                flt = new Float(str);
3098                res[1] = flt.floatValue();
3099                return res;
3100            }
3101        } catch (Exception e) {
3102            logger.error("problem getting the line+element rez", e);
3103        }
3104        return res;
3105    }
3106
3107    /**
3108     * Read the adde text url and return the lines of text.
3109     * If unsuccessful return null.
3110     *
3111     * @param url adde url to a text file
3112     *
3113     * @return List of lines or {@code null} if in error.
3114     */
3115    
3116    protected List readTextLines(String url) {
3117        AddeTextReader reader = new AddeTextReader(url);
3118        List lines = null;
3119        if ("OK".equals(reader.getStatus())) {
3120            lines = reader.getLinesOfText();
3121        }
3122        return lines;
3123    }
3124
3125    /**
3126     * Create the first part of the ADDE request URL
3127     * 
3128     * @return ADDE URL prefix
3129     */
3130    
3131    protected String getUrl() {
3132        String str = source;
3133        str = str.replaceFirst("imagedata", "text");
3134        int indx = str.indexOf("VERSION");
3135        str = str.substring(0, indx);
3136        str = str.concat("file=SATBAND");
3137        return str;
3138    }
3139
3140    public Hashtable getSourceProps() {
3141        return this.sourceProps;
3142    }
3143
3144    public void setSourceProps(Hashtable sourceProps) {
3145        this.sourceProps = sourceProps;
3146    }
3147
3148    public String getChoiceName() {
3149        return this.choiceName;
3150    }
3151
3152    public void setChoiceName(String choiceName) {
3153        this.choiceName = choiceName;
3154    }
3155
3156    public String getSavePlace() {
3157        return this.savePlace;
3158    }
3159
3160    public void setSavePlace(String savePlace) {
3161        this.savePlace = savePlace;
3162    }
3163
3164    public double getSaveLat() {
3165        return this.saveLat;
3166    }
3167
3168    public void setSaveLat(double saveLat) {
3169        this.saveLat = saveLat;
3170    }
3171
3172    public double getSaveLon() {
3173        return this.saveLon;
3174    }
3175
3176    public void setSaveLon(double saveLon) {
3177        this.saveLon = saveLon;
3178    }
3179
3180    public int getSaveNumLine() {
3181        return this.saveNumLine;
3182    }
3183
3184    public void setSaveNumLine(int saveNumLine) {
3185        this.saveNumLine = saveNumLine;
3186    }
3187
3188    public int getSaveNumEle() {
3189        return this.saveNumEle;
3190    }
3191
3192    public void setSaveNumEle(int saveNumEle) {
3193        this.saveNumEle = saveNumEle;
3194    }
3195
3196    public int getSaveLineMag() {
3197        return this.saveLineMag;
3198    }
3199
3200    public void setSaveLineMag(int saveLineMag) {
3201        this.saveLineMag = saveLineMag;
3202    }
3203
3204    public int getSaveEleMag() {
3205        return this.saveEleMag;
3206    }
3207
3208    public void setSaveEleMag(int saveEleMag) {
3209        this.saveEleMag = saveEleMag;
3210    }
3211
3212    public String getSource() {
3213        return this.source;
3214    }
3215
3216    public void setSource(String source) {
3217        this.source = source;
3218    }
3219
3220    public boolean getShowPreview() {
3221        return this.showPreview;
3222    }
3223
3224    public void setShowPreview(boolean showPreview) {
3225        this.showPreview = showPreview;
3226    }
3227
3228    public boolean getSaveShowPreview() {
3229        return this.saveShowPreview;
3230    }
3231
3232    public void setSaveShowPreview(boolean saveShowPreview) {
3233        this.saveShowPreview = saveShowPreview;
3234    }
3235
3236    private void getSaveComponents() {
3237        saveCoordType = this.laLoSel.getCoordinateType();
3238        savePlace = this.laLoSel.getPlace();
3239        if (saveCoordType.equals(this.laLoSel.getLatLonType())) {
3240            saveLat = this.laLoSel.getLatitude();
3241            saveLon = this.laLoSel.getLongitude();
3242        }
3243        saveNumLine = this.laLoSel.getNumLines();
3244        saveNumEle = this.laLoSel.getNumEles();
3245        saveLineMag = this.laLoSel.getLineMag();
3246        saveEleMag = this.laLoSel.getElementMag();
3247    }
3248
3249    /**
3250     * Return the list of times held by the DataSelection member.
3251     *
3252     * @return  DataSelection times
3253     */
3254    
3255    public List getDateTimeSelection() {
3256//        return super.getDateTimeSelection();
3257        DataSelection s = getDataSelection();
3258        if (s == null) {
3259            logger.trace("oh no getDataSelection is null :(");
3260            return null;
3261        } else {
3262            return s.getTimes();
3263        }
3264    }
3265
3266    /**
3267     * Set the list of selected times for this data source. This is used
3268     * for XML persistence.
3269     *
3270     * @param selectedTimes   List of selected times
3271     */
3272    
3273    public void setDateTimeSelection(List selectedTimes) {
3274//        //Check to see if we need to convert the absolute times into an index list.
3275//        if (holdsDateTimes(selectedTimes) && (timesList != null)) {
3276//            selectedTimes = Misc.getIndexList(selectedTimes,
3277//                getAllDateTimes());
3278//        }
3279//        getDataSelection().setTimes(selectedTimes);
3280        super.setDateTimeSelection(selectedTimes);
3281        List selected = getDateTimeSelection();
3282        logger.trace("incoming: {} result: {}", selectedTimes, selected);
3283    }
3284
3285    protected boolean canDoProgressiveResolution() {
3286        return false;
3287    }
3288
3289    public boolean getIsProgressiveResolution() {
3290        return false;
3291    }
3292
3293    public void setIsProgressiveResolution(boolean isPG) {
3294
3295    }
3296
3297    public boolean getMatchDisplayRegion() {
3298        return false;
3299    }
3300
3301    public static class BundlePreviewSelection extends DataSelectionComponent {
3302        final String label;
3303        public BundlePreviewSelection(final String label) {
3304            super(label);
3305            this.label = label;
3306        }
3307
3308        @Override protected JComponent doMakeContents() {
3309            // TODO Auto-generated method stub
3310            JPanel panel = new JPanel();
3311            panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
3312            JLabel label1 = new JLabel("Area coverage has been defined by the data bundle;");
3313            JLabel label2 = new JLabel("further subsetting is not currently supported.");
3314            label1.setAlignmentX(Component.CENTER_ALIGNMENT);
3315            label2.setAlignmentX(Container.CENTER_ALIGNMENT);
3316            panel.add(label1);
3317            panel.add(label2);
3318            return panel;
3319        }
3320
3321        @Override public void applyToDataSelection(DataSelection dataSelection) {
3322        }
3323
3324        /**
3325         * Overridden to disable these dummy tabs from showing up in properties
3326         * dialog.
3327         */
3328        @Override public boolean getShowInControlProperties() {
3329            return false;
3330        }
3331    }
3332}