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