001/*
002 * This file is part of McIDAS-V
003 *
004 * Copyright 2007-2016
005 * Space Science and Engineering Center (SSEC)
006 * University of Wisconsin - Madison
007 * 1225 W. Dayton Street, Madison, WI 53706, USA
008 * https://www.ssec.wisc.edu/mcidas
009 * 
010 * All Rights Reserved
011 * 
012 * McIDAS-V is built on Unidata's IDV and SSEC's VisAD libraries, and
013 * some McIDAS-V source code is based on IDV and VisAD source code.  
014 * 
015 * McIDAS-V is free software; you can redistribute it and/or modify
016 * it under the terms of the GNU Lesser Public License as published by
017 * the Free Software Foundation; either version 3 of the License, or
018 * (at your option) any later version.
019 * 
020 * McIDAS-V is distributed in the hope that it will be useful,
021 * but WITHOUT ANY WARRANTY; without even the implied warranty of
022 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
023 * GNU Lesser Public License for more details.
024 * 
025 * You should have received a copy of the GNU Lesser Public License
026 * along with this program.  If not, see http://www.gnu.org/licenses.
027 */
028
029package edu.wisc.ssec.mcidasv.chooser.adde;
030
031import static javax.swing.GroupLayout.DEFAULT_SIZE;
032import static javax.swing.GroupLayout.PREFERRED_SIZE;
033import static javax.swing.GroupLayout.Alignment.BASELINE;
034import static javax.swing.GroupLayout.Alignment.LEADING;
035import static javax.swing.LayoutStyle.ComponentPlacement.RELATED;
036
037import java.awt.Component;
038import java.util.ArrayList;
039import java.util.Hashtable;
040import java.util.Iterator;
041import java.util.List;
042import java.util.StringTokenizer;
043
044import javax.swing.GroupLayout;
045import javax.swing.JComboBox;
046import javax.swing.JComponent;
047import javax.swing.JLabel;
048import javax.swing.JPanel;
049import javax.swing.JTabbedPane;
050
051import org.w3c.dom.Element;
052
053import edu.wisc.ssec.mcidas.AreaDirectory;
054import edu.wisc.ssec.mcidas.AreaDirectoryList;
055import edu.wisc.ssec.mcidas.AreaFileException;
056import edu.wisc.ssec.mcidas.McIDASUtil;
057
058import ucar.unidata.data.imagery.AddeImageInfo;
059import ucar.unidata.data.imagery.ImageDataSource;
060import ucar.unidata.idv.chooser.IdvChooserManager;
061import ucar.unidata.idv.chooser.adde.AddeServer;
062import ucar.unidata.metdata.NamedStationTable;
063import ucar.unidata.util.GuiUtils;
064import ucar.unidata.util.LogUtil;
065import ucar.unidata.util.Misc;
066
067
068import edu.wisc.ssec.mcidasv.util.McVGuiUtils;
069
070/**
071 * Widget to select NEXRAD radar images from a remote ADDE server
072 * Displays a list of the descriptors (names) of the radar datasets
073 * available for a particular ADDE group on the remote server.
074 *
075 * @author Don Murray
076 */
077public class AddeRadarChooser extends AddeImageChooser {
078
079    /** Use to list the stations */
080    protected static final String VALUE_LIST = "list";
081
082    /** This is the list of properties that are used in the advanced gui */
083    private static final String[] RADAR_PROPS = { PROP_UNIT };
084
085    /** This is the list of labels used for the advanced gui */
086    private static final String[] RADAR_LABELS = { "Data Type:" };
087
088    /** Am I currently reading the stations */
089    private boolean readingStations = false;
090
091    /** handle on the station update task */
092    private Object readStationTask;
093
094    /** station table */
095    private List nexradStations;
096
097
098
099    /**
100     * Construct an Adde image selection widget displaying information
101     * for the specified dataset located on the specified server.
102     *
103     *
104     *
105     * @param mgr The chooser manager
106     * @param root The chooser.xml node
107     */
108    public AddeRadarChooser(IdvChooserManager mgr, Element root) {
109        super(mgr, root);
110        this.nexradStations =
111            getIdv().getResourceManager().findLocationsByType("radar");
112    }
113
114    /**
115     * get the adde server grup type to use
116     *
117     * @return group type
118     */
119    protected String getGroupType() {
120        return AddeServer.TYPE_RADAR;
121    }
122
123    /**
124     * Overwrite base class method to return the correct name
125     * (used for labeling, etc.)
126     *
127     * @return  data name specific to this selector
128     */
129    public String getDataName() {
130        return "Radar Data";
131    }
132
133    @Override public String getDataType() {
134        return "RADAR";
135    }
136
137    /**
138     * _more_
139     *
140     * @return _more_
141     */
142    public String getDescriptorLabel() {
143        return "Product";
144    }
145
146    /**
147     * Get the size of the image list
148     *
149     * @return the image list size
150     */
151    protected int getImageListSize() {
152        return 6;
153    }
154    
155    /**
156     * Get a description of the currently selected dataset
157     *
158     * @return the data set description.
159     */
160    public String getDatasetName() {
161        return getSelectedStation() + " (" + super.getDatasetName() + ")";
162    }
163
164    /**
165     * Method to call if the server changed.
166     */
167    protected void connectToServer() {
168        clearStations();
169        super.connectToServer();
170        setAvailableStations();
171    }
172
173    /**
174     * Check if we are ready to read times
175     *
176     * @return  true if times can be read
177     */
178    protected boolean canReadTimes() {
179        return super.canReadTimes() && (getSelectedStation() != null);
180    }
181
182    /**
183     * Get the advanced property names
184     *
185     * @return array of advanced properties
186     */
187    protected String[] getAdvancedProps() {
188        return RADAR_PROPS;
189    }
190
191    /**
192     * Get the labels for the advanced properties
193     *
194     * @return array of labels
195     */
196    protected String[] getAdvancedLabels() {
197        return RADAR_LABELS;
198    }
199
200    /**
201     * Update labels, etc.
202     */
203    protected void updateStatus() {
204        super.updateStatus();
205        if (getState() != STATE_CONNECTED) {
206            clearStations();
207        }
208        if (readStationTask!=null) {
209            if(taskOk(readStationTask)) {
210                setStatus("Reading available stations from server");
211            } else {
212                readStationTask  = null;
213                setState(STATE_UNCONNECTED);
214            }
215        }
216    }
217
218    /**
219     * A new station was selected. Update the gui.
220     *
221     * @param stations List of selected stations
222     */
223    protected void newSelectedStations(List stations) {
224        super.newSelectedStations(stations);
225        descriptorChanged();
226    }
227
228    /**
229     *  Generate a list of radar ids for the id list.
230     */
231    private void setAvailableStations() {
232        readStationTask = startTask();
233        clearSelectedStations();
234        updateStatus();
235        List stations = readStations();
236        if(stopTaskAndIsOk(readStationTask)) {
237            readStationTask = null;
238            if (stations != null) {
239                getStationMap().setStations(stations);
240            } else {
241                clearStations();
242            }
243            updateStatus();
244            revalidate();
245        } else {
246            //User pressed cancel
247            setState(STATE_UNCONNECTED);
248            return;
249        }
250    }
251
252    /**
253     * Generate a list of radar ids for the id list.
254     *
255     * @return  list of station IDs
256     */
257    private List readStations() {
258        ArrayList stations = new ArrayList();
259        try {
260            if ((descriptorNames == null) || (descriptorNames.length == 0)) {
261                return stations;
262            }
263            StringBuffer buff        = getGroupUrl(REQ_IMAGEDIR, getGroup());
264            String       descrForIds = descriptorNames[0];
265            // try to use base reflectivity if it's available.
266            for (int i = 0; i < descriptorNames.length; i++) {
267                if ((descriptorNames[i] != null)
268                        && descriptorNames[i].toLowerCase().startsWith(
269                            "base")) {
270                    descrForIds = descriptorNames[i];
271                    break;
272                }
273            }
274            appendKeyValue(buff, PROP_DESCR,
275                           getDescriptorFromSelection(descrForIds));
276            appendKeyValue(buff, PROP_ID, VALUE_LIST);
277            Hashtable         seen    = new Hashtable();
278            AreaDirectoryList dirList =
279                new AreaDirectoryList(buff.toString());
280            for (Iterator it = dirList.getDirs().iterator(); it.hasNext(); ) {
281                AreaDirectory ad = (AreaDirectory) it.next();
282                String stationId =
283                    McIDASUtil.intBitsToString(ad.getValue(20)).trim();
284                //Check for uniqueness
285                if (seen.get(stationId) != null) {
286                    continue;
287                }
288                seen.put(stationId, stationId);
289                //System.err.println ("id:" + stationId);
290                Object station = findStation(stationId);
291                if (station != null) {
292                    stations.add(station);
293                }
294            }
295        } catch (AreaFileException e) {
296            String msg = e.getMessage();
297            if (msg.toLowerCase().indexOf(
298                    "no images meet the selection criteria") >= 0) {
299                LogUtil.userErrorMessage(
300                    "No stations could be found on the server");
301            } else {
302                handleConnectionError(e);
303            }
304            stations = new ArrayList();
305            setState(STATE_UNCONNECTED);
306        }
307        return stations;
308    }
309
310    /**
311     * Find the station for the given ID
312     *
313     * @param stationId  the station ID
314     *
315     * @return  the station or null if not found
316     */
317    private Object findStation(String stationId) {
318        for (int i = 0; i < nexradStations.size(); i++) {
319            NamedStationTable table =
320                (NamedStationTable) nexradStations.get(i);
321            Object station = table.get(stationId);
322            if (station != null) {
323                return station;
324            }
325        }
326        return null;
327    }
328
329    public void doCancel() {
330        readStationTask = null;
331        super.doCancel();
332    }
333
334    /**
335     * Get the list of properties for the base URL
336     * @return list of properties
337     */
338    protected String[] getBaseUrlProps() {
339        return new String[] { PROP_DESCR, PROP_ID, PROP_UNIT, PROP_SPAC,
340                              PROP_BAND, PROP_USER, PROP_PROJ, };
341    }
342
343    /**
344     * Overwrite the base class method to return the default property value
345     * for PROP_ID.
346     *
347     * @param prop The property
348     * @param ad The area directory
349     * @param forDisplay Is this to show the end user in the gui.
350     *
351     * @return The value of the property
352     */
353    protected String getDefaultPropValue(String prop, AreaDirectory ad,
354                                         boolean forDisplay) {
355        if (prop.equals(PROP_ID)) {
356            return getSelectedStation();
357        }
358        if (prop.equals(PROP_SPAC)) {
359            // Don't want this to default to "1" or it will break
360            // Hydrometeor Classification product...see inquiry 1518
361            return "4";
362        }
363        return super.getDefaultPropValue(prop, ad, forDisplay);
364    }
365
366    /**
367     * Get a description of the properties
368     *
369     * @return  a description
370     */
371    protected String getPropertiesDescription() {
372        StringBuilder buf = new StringBuilder();
373        if (unitComboBox != null) {
374            buf.append(getAdvancedLabels()[0]);
375            buf.append(' ');
376            buf.append(unitComboBox.getSelectedItem());
377        }
378        return buf.toString();
379    }
380
381    /**
382     * get properties
383     *
384     * @param ht properties
385     */
386    protected void getDataSourceProperties(Hashtable ht) {
387        unitComboBox.setSelectedItem(ALLUNITS);
388        super.getDataSourceProperties(ht);
389        ht.put(ImageDataSource.PROP_IMAGETYPE, ImageDataSource.TYPE_RADAR);
390    }
391    
392    /**
393     * Get the time popup widget
394     *
395     * @return  a widget for selecing the day
396     */
397    protected JComponent getExtraTimeComponent() {
398        JPanel filler = new JPanel();
399        McVGuiUtils.setComponentHeight(filler, new JComboBox());
400        return filler;
401    }
402    
403    /**
404     * Make the UI for this selector.
405     *
406     * @return The gui
407     */
408    public JComponent doMakeContents() {      
409        JPanel myPanel = new JPanel();
410                
411        JLabel stationLabel = McVGuiUtils.makeLabelRight("Station:");
412        addServerComp(stationLabel);
413
414        JComponent stationPanel = getStationMap();
415        registerStatusComp("stations", stationPanel);
416        addServerComp(stationPanel);
417        
418        JLabel timesLabel = McVGuiUtils.makeLabelRight("Times:");
419        addDescComp(timesLabel);
420        
421        JPanel timesPanel = makeTimesPanel();
422        timesPanel.setBorder(javax.swing.BorderFactory.createEtchedBorder());
423        addDescComp(timesPanel);
424        
425        // We need to create this but never show it... AddeImageChooser requires it to be instantiated
426        unitComboBox = new JComboBox();
427        
428        enableWidgets();
429
430        GroupLayout layout = new GroupLayout(myPanel);
431        myPanel.setLayout(layout);
432        layout.setHorizontalGroup(
433            layout.createParallelGroup(LEADING)
434            .addGroup(layout.createSequentialGroup()
435                .addGroup(layout.createParallelGroup(LEADING)
436                    .addGroup(layout.createSequentialGroup()
437                        .addComponent(descriptorLabel)
438                        .addGap(GAP_RELATED)
439                        .addComponent(descriptorComboBox))
440                    .addGroup(layout.createSequentialGroup()
441                        .addComponent(stationLabel)
442                        .addGap(GAP_RELATED)
443                        .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
444                    .addGroup(layout.createSequentialGroup()
445                        .addComponent(timesLabel)
446                        .addGap(GAP_RELATED)
447                        .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))))
448        );
449        layout.setVerticalGroup(
450            layout.createParallelGroup(LEADING)
451            .addGroup(layout.createSequentialGroup()
452                .addGroup(layout.createParallelGroup(BASELINE)
453                    .addComponent(descriptorLabel)
454                    .addComponent(descriptorComboBox))
455                .addPreferredGap(RELATED)
456                .addGroup(layout.createParallelGroup(LEADING)
457                    .addComponent(stationLabel)
458                    .addComponent(stationPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE))
459                .addPreferredGap(RELATED)
460                .addGroup(layout.createParallelGroup(LEADING)
461                    .addComponent(timesLabel)
462                    .addComponent(timesPanel, PREFERRED_SIZE, DEFAULT_SIZE, Short.MAX_VALUE)))
463        );
464        
465        setInnerPanel(myPanel);
466        return super.doMakeContents(true);
467    }
468    
469    /**
470     * Get the default value for a key
471     * 
472     * @return null for SIZE, else super
473     */
474    protected String getDefault(String property, String dflt) {
475        if (PROP_SIZE.equals(property)) {
476            return dflt;
477        }
478        return super.getDefault(property, dflt);
479    }
480    
481    /**
482     * Make an AddeImageInfo from a URL and an AreaDirectory
483     * 
484     * @param dir
485     *            AreaDirectory
486     * @param isRelative
487     *            true if is relative
488     * @param num
489     *            number (for relative images)
490     * 
491     * @return corresponding AddeImageInfo
492     */
493    protected AddeImageInfo makeImageInfo(AreaDirectory dir,
494            boolean isRelative, int num) {
495        AddeImageInfo info = new AddeImageInfo(getAddeServer().getName(),
496                AddeImageInfo.REQ_IMAGEDATA, getGroup(), getDescriptor());
497        if (isRelative) {
498            info.setDatasetPosition((num == 0) ? 0 : -num);
499        } else {
500            info.setStartDate(dir.getNominalTime());
501        }
502        setImageInfoProps(info, getMiscKeyProps(), dir);
503        setImageInfoProps(info, getBaseUrlProps(), dir);
504
505        info.setLocateKey(PROP_LINELE);
506        info.setLocateValue("0 0 F");
507        info.setPlaceValue("ULEFT");
508        
509        String magKey = getPropValue(PROP_MAG, dir);
510        int lmag = 1;
511        int emag = 1;
512        StringTokenizer tok = new StringTokenizer(magKey);
513        lmag = (int) Misc.parseNumber((String) tok.nextElement());
514        if (tok.hasMoreTokens()) {
515            emag = (int) Misc.parseNumber((String) tok.nextElement());
516        } else {
517            emag = lmag;
518        }
519        info.setLineMag(lmag);
520        info.setElementMag(emag);
521
522        int lines = dir.getLines();
523        int elems = dir.getElements();
524        String sizeKey = getPropValue(PROP_SIZE, dir);
525        tok = new StringTokenizer(sizeKey);
526        String size = (String) tok.nextElement();
527        if (!size.equalsIgnoreCase("all")) {
528            lines = (int) Misc.parseNumber(size);
529            if (tok.hasMoreTokens()) {
530                elems = (int) Misc.parseNumber((String) tok.nextElement());
531            } else {
532                elems = lines;
533            }
534        }
535        info.setLines(lines);
536        info.setElements(elems);
537        /*
538         * System.out.println("url = " + info.getURLString().toLowerCase() +
539         * "\n");
540         */
541        return info;
542    }
543
544    /**
545     * Set the relative and absolute extra components.
546     */
547    @Override protected JPanel makeTimesPanel() {
548        // show the time driver if the rest of the choosers are doing so.
549        JPanel timesPanel =
550            super.makeTimesPanel(false, true, getIdv().getUseTimeDriver());
551
552        // Make a new timesPanel that has extra components tacked on the
553        // bottom, inside the tabs
554        Component[] comps = timesPanel.getComponents();
555
556        if ((comps.length == 1) && (comps[0] instanceof JTabbedPane)) {
557            timesCardPanelExtra = new GuiUtils.CardLayoutPanel();
558            timesCardPanelExtra.add(new JPanel(), "relative");
559            timesCardPanelExtra.add(getExtraTimeComponent(), "absolute");
560            timesPanel = GuiUtils.centerBottom(comps[0], timesCardPanelExtra);
561        }
562        return timesPanel;
563    }
564}