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