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.data.adde;
030
031
032import java.util.ArrayList;
033import java.util.Collections;
034import java.util.Map;
035import java.util.StringTokenizer;
036
037import edu.wisc.ssec.mcidas.McIDASUtil;
038import edu.wisc.ssec.mcidas.adde.AddeException;
039import edu.wisc.ssec.mcidas.adde.AddePointDataReader;
040
041import visad.DateTime;
042import visad.Unit;
043
044import ucar.unidata.beans.NonVetoableProperty;
045import ucar.unidata.data.sounding.SoundingAdapter;
046import ucar.unidata.data.sounding.SoundingAdapterImpl;
047import ucar.unidata.data.sounding.SoundingOb;
048import ucar.unidata.data.sounding.SoundingStation;
049import ucar.unidata.util.LogUtil;
050import ucar.unidata.util.Misc;
051import ucar.unidata.util.StringUtil;
052import ucar.visad.UtcDate;
053import ucar.visad.Util;
054import ucar.visad.quantities.GeopotentialAltitude;
055
056import edu.wisc.ssec.mcidasv.chooser.adde.AddeChooser;
057
058/**
059 * Class for retrieving upper air data from an ADDE remote server. Creates
060 * a SoundingOb for each of the stations on the remote server for the
061 * latest available data.
062 */
063public class AddeSoundingAdapter extends SoundingAdapterImpl implements SoundingAdapter {
064
065        /** observed or satellite sounding? */
066        private boolean satelliteSounding = false;
067        
068        /** these are only really used for satellite soundings */
069        private String satelliteTime = "";
070        private String satellitePixel = "";
071        
072    /** parameter identifier */
073    private static final String P_PARAM = "param";
074
075    /** number of obs identifier */
076    private static final String P_NUM = "num";
077
078    /** all obs identifier */
079    private static final String P_ALL = "all";
080
081    /** number of obs identifier */
082    private static final String P_POS = "pos";
083
084    /** group identifier */
085    private static final String P_GROUP = "group";
086
087    /** descriptor identifier */
088    private static final String P_DESCR = "descr";
089
090    /** select identifier */
091    private static final String P_SELECT = "select";
092
093    /** URL type identifier */
094    private static final String URL_ROOT = "/point";
095
096    /** URL protocol identifier */
097    private static final String URL_PROTOCOL = "adde";
098
099    /** server propert */
100    private NonVetoableProperty serverProperty;
101
102    /** mandatory data set property */
103    private NonVetoableProperty mandatoryDatasetProperty;
104
105    /** significant data set property */
106    private NonVetoableProperty significantDatasetProperty;
107
108    /** stations property */
109    private NonVetoableProperty stationsProperty;
110
111    /** sounding times property */
112    private NonVetoableProperty soundingTimesProperty;
113
114    /** mandatory data group name */
115    private String manGroup;
116
117    /** mandatory data descriptor */
118    private String manDescriptor;
119
120    /** sig data group name */
121    private String sigGroup = null;
122
123    /** sig data descriptor */
124    private String sigDescriptor = null;
125
126    /** use main hours only */
127    private boolean mainHours = false;
128
129    /** name of mandP pressure variable */
130    private String prMandPVar = "p";
131
132    /** name of mandP height variable */
133    private String htMandPVar = "z";
134
135    /** name of mandP temp variable */
136    private String tpMandPVar = "t";
137
138    /** name of mandP dewpoint variable */
139    private String tdMandPVar = "td";
140
141    /** name of mandP wind speed variable */
142    private String spdMandPVar = "spd";
143
144    /** name of mandP wind dir variable */
145    private String dirMandPVar = "dir";
146
147    /** name of day variable */
148    private String dayVar = "day";
149
150    /** name of time variable */
151    private String timeVar = "time";
152
153    /** name of station id variable */
154    private String idVar = "idn";
155
156    /** name of station latitude variable */
157    private String latVar = "lat";
158
159    /** name of station longitude variable */
160    private String lonVar = "lon";
161
162    /** name of station elevation variable */
163    private String eleVar = "zs";
164
165
166    /** server name */
167    private String server;
168
169    /** mandatory dataset name */
170    private String mandDataset;
171
172    /** significant dataset name */
173    private String sigDataset;
174
175    /** default server */
176    private String defaultServer = "adde.unidata.ucar.edu";
177
178    /** default mandatory data set */
179    private String defaultMandDataset = "rtptsrc/uppermand";
180
181    /** default significant dataset */
182    private String defaultSigDataset = "rtptsrc/uppersig";
183
184    /** Accounting information */
185    private static String user = "user";
186    private static String proj = "0";
187
188    protected boolean firstTime = true;
189    protected boolean retry = true;
190
191    /** Used to grab accounting information for a currently selected server. */
192    private AddeChooser addeChooser;
193    /**
194        
195     * Construct an empty AddeSoundingAdapter
196     */
197    public AddeSoundingAdapter() {
198        super("AddeSoundingAdapter");
199    }
200
201    /**
202     * Retreive upper air data from a remote ADDE server using only
203     * mandatory data.
204     *
205     * @param    server   name or IP address of remote server
206     *
207     * @throws Exception (AddeException) if there is no data available or there
208     *              is trouble connecting to the remote server
209     */
210    public AddeSoundingAdapter(String server) throws Exception {
211        this(server, null);
212    }
213
214    /**
215     * Retreive upper air data from a remote ADDE server using only
216     * mandatory data.
217     *
218     * @param    server   name or IP address of remote server
219     * @param    dataset  name of ADDE dataset (group/descriptor)
220     *
221     * @throws  Exception (AddeException) if there is no data available or there
222     *              is trouble connecting to the remote server
223     */
224    public AddeSoundingAdapter(String server, String dataset)
225            throws Exception {
226        this(server, dataset, null);
227    }
228
229
230
231    /**
232     * Retreive upper air data from a remote ADDE server using only
233     * mandatory data.
234     *
235     * @param    server       name or IP address of remote server
236     * @param    mandDataset  name of mandatory level upper air ADDE
237     *                        dataset (group/descriptor)
238     * @param    sigDataset   name of significant level upper air ADDE
239     *                        dataset (group/descriptor)
240     *
241     * @throws Exception (AddeException) if there is no data available
242     *            or there is trouble connecting to the remote server
243     */
244    public AddeSoundingAdapter(String server, String mandDataset,
245                               String sigDataset)
246            throws Exception {
247        this(server, mandDataset, sigDataset, false);
248    }
249
250    /**
251     * Retreive upper air data from a remote ADDE server using only
252     * mandatory data.
253     *
254     * @param    server       name or IP address of remote server
255     * @param    mandDataset  name of mandatory level upper air ADDE
256     *                        dataset (group/descriptor)
257     * @param    sigDataset   name of significant level upper air ADDE
258     *                        dataset (group/descriptor)
259     * @param    mainHours    only get data for main (00 & 12Z) hours
260     *
261     * @throws Exception (AddeException) if there is no data available
262     *            or there is trouble connecting to the remote server
263     */
264    public AddeSoundingAdapter(String server, String mandDataset,
265                               String sigDataset, boolean mainHours)
266            throws Exception {
267        this(server, mandDataset, sigDataset, false, null);
268    }
269
270
271    public AddeSoundingAdapter(String server, String mandDataset, 
272                String sigDataset, boolean mainHours, AddeChooser chooser) 
273    throws Exception {
274        super("AddeSoundingAdapter");
275        this.server      = server;
276        this.mandDataset = mandDataset;
277        this.sigDataset  = sigDataset;
278        this.mainHours   = mainHours;
279        this.satelliteSounding = false;
280        this.satelliteTime = "";
281        this.satellitePixel = "";
282        this.addeChooser = chooser;
283        init();
284    }
285    
286    public AddeSoundingAdapter(String server, String mandDataset, 
287                String sigDataset, String satelliteTime, String satellitePixel, AddeChooser chooser) 
288    throws Exception 
289    {
290        super("AddeSoundingAdapter");
291        this.server      = server;
292        this.mandDataset = mandDataset;
293        this.sigDataset  = sigDataset;
294        this.mainHours   = false;
295        this.satelliteSounding = true;
296        this.satelliteTime = satelliteTime;
297        this.satellitePixel = satellitePixel;
298        this.addeChooser = chooser;
299        init();
300    }
301    
302    /**
303     * Initialize the class.  Populate the variable list and get
304     * the server and dataset information.
305     *
306     * @throws Exception   problem occurred
307     */
308    protected void init() throws Exception {
309        if (haveInitialized) {
310            return;
311        }
312        super.init();
313
314        getVariables();
315
316        if (server == null) {
317            server = defaultServer;
318        }
319
320        if (mandDataset == null) {
321            mandDataset = defaultMandDataset;
322        }
323
324        if (sigDataset == null) {
325            sigDataset = defaultSigDataset;
326        }
327
328        // set up the properties
329        addProperty(serverProperty = new NonVetoableProperty(this, "server"));
330        serverProperty.setValue(server);
331
332        addProperty(mandatoryDatasetProperty = new NonVetoableProperty(this,
333                "mandatoryDataset"));
334        mandatoryDatasetProperty.setValue(mandDataset);
335
336        addProperty(significantDatasetProperty =
337            new NonVetoableProperty(this, "significantDataset"));
338        significantDatasetProperty.setValue(sigDataset);
339
340        addProperty(stationsProperty = new NonVetoableProperty(this,
341                "stations"));
342        addProperty(soundingTimesProperty = new NonVetoableProperty(this,
343                "soundingTimes"));
344        loadStations();
345    }
346
347
348    /**
349     *  Utility method that calls McIDASUtil.intBitsToString
350     *  to get a string to compare to the given parameter s
351     *
352     * @param v    integer string value
353     * @param s    string to compare
354     * @return  true if they are equal
355     */
356    private boolean intEqual(int v, String s) {
357        return (McIDASUtil.intBitsToString(v).equals(s));
358    }
359
360
361
362    /**
363     * Return the given String in single quotes
364     *
365     * @param s Add single quotes to the string for select clauses.
366     * @return  single quoted string (ex:  'foo')
367     */
368    private String sQuote(String s) {
369        return "'" + s + "'";
370    }
371
372
373    /**
374     * Assemble the URL from the given URL argument array. This turns around
375     * and calls {@link #makeUrl(String, String[])}, passing in the
376     * {@link #URL_ROOT} ({@literal "/point"}) and the {@code URL_ROOT} to use.
377     *
378     * @param args URL arguments, key value pairs
379     *             (ex: {@code arg[0]=arg[1]&arg[2]=arg[3]...})
380     * @return Associated URL.
381     */
382    private String makeUrl(String[] args) {
383        return makeUrl(URL_ROOT, args);
384    }
385
386    /**
387     * Assemble the url from the given {@code urlRoot} and URL argument array.
388     * This returns:
389     * {@code "URL_PROTOCOL://server urlRoot ?arg[0]=arg[1]&arg[2]=arg[3]...}
390     *
391     * @param urlRoot Root for the URL
392     * @param args    Key/value pair arguments.
393     *
394     * @return ADDE URL.
395     */
396    private String makeUrl(String urlRoot, String[] args) {
397        return Misc.makeUrl(URL_PROTOCOL, server, urlRoot, args);
398    }
399
400
401    /**
402     * Update this adapter for new data
403     */
404    public void update() {
405        checkInit();
406        try {
407            loadStations();
408        } catch (Exception exc) {
409            LogUtil.logException("Error updating AddeSoundingAdapter", exc);
410        }
411    }
412
413
414    /**
415     * Initialize the times, stations and soundings lists.
416     * Load the data into them.
417     */
418    private void loadStations() {
419        times     = new ArrayList(8);
420        stations  = new ArrayList(100);
421        soundings = new ArrayList(100);
422        try {
423            if ((server != null) && (mandDataset != null)) {
424                loadStationsInner();
425            }
426        } catch (Exception excp) {
427            if (firstTime) {
428                String aes = excp.toString();
429                if ((aes.indexOf("Accounting data")) >= 0) {
430                    if (addeChooser != null && addeChooser.canAccessServer()) {
431                        Map<String, String> acctInfo = addeChooser.getAccountingInfo();
432                        user = acctInfo.get("user");
433                        proj = acctInfo.get("proj");
434                    }
435                }
436                firstTime = false;
437                update();
438            }
439        }
440        stationsProperty.setValueAndNotifyListeners(stations);
441        soundingTimesProperty.setValueAndNotifyListeners(times);
442    }
443
444    private String getServer() {
445        return this.server;
446    }
447
448    /**
449     * Initialize the group and descriptor strings
450     */
451    private void initGroupAndDescriptors() {
452        if (manGroup == null) {
453            StringTokenizer tok = new StringTokenizer(mandDataset, "/");
454            if (tok.countTokens() != 2) {
455                throw new IllegalStateException(
456                    "Illegal mandatory dataset name " + mandDataset);
457            }
458            manGroup      = tok.nextToken();
459            manDescriptor = tok.nextToken();
460        }
461        if ((sigDataset != null) && (sigGroup == null)) {
462            StringTokenizer tok = new StringTokenizer(sigDataset, "/");
463            if (tok.countTokens() != 2) {
464                throw new IllegalStateException(
465                    "Illegal significant dataset name " + mandDataset);
466            }
467            sigGroup      = tok.nextToken();
468            sigDescriptor = tok.nextToken();
469        }
470    }
471
472
473    /**
474     * Actually do the work of loading the stations
475     *
476     * @throws AddeException  problem accessing data
477     */
478    private void loadStationsInner() throws AddeException {
479        initGroupAndDescriptors();
480        String request = "";
481        if (!satelliteSounding) {
482                request = makeUrl(new String[] {
483                    P_GROUP, manGroup, P_DESCR, manDescriptor, P_PARAM,
484                    StringUtil.join(new String[] {
485                        dayVar, timeVar, idVar, latVar, lonVar, eleVar
486                    }), P_NUM, P_ALL, P_POS, P_ALL
487                }) + getManUserProj() + getStationsSelectString();
488        }
489        else {
490                request = makeUrl(new String[] {
491                    P_GROUP, manGroup, P_DESCR, manDescriptor, P_PARAM,
492                    StringUtil.join(new String[] {
493                        dayVar, timeVar, idVar, latVar, lonVar
494                    }), P_NUM, P_ALL, P_POS, P_ALL
495                }) + getManUserProj() + getStationsSelectString();
496        }
497        dbPrint(request);
498
499        //System.err.println("loading stations: " + request);
500
501        AddePointDataReader dataReader = new AddePointDataReader(request);
502        String[]            units      = dataReader.getUnits();
503        int[]               scales     = dataReader.getScales();
504        int[][]             data       = dataReader.getData();
505
506        for (int i = 0; i < data[0].length; i++) {
507            int    day   = data[0][i];
508            int    time  = data[1][i];
509            String wmoID = Integer.toString(data[2][i]);
510            double lat   = scaleValue(data[3][i], scales[3]);
511            double lon   = scaleValue(data[4][i], scales[4]);
512            lon = -lon;  // change from McIDAS to eastPositive
513            double elev = 0;
514            if (!satelliteSounding)
515                elev = scaleValue(data[5][i], scales[5]);
516            try {
517                SoundingStation s = new SoundingStation(wmoID, lat, lon,
518                                        elev);
519                if ( !(stations.contains(s))) {
520                    stations.add(s);
521                }
522                DateTime dt = new DateTime(McIDASUtil.mcDayTimeToSecs(day,
523                                  time));
524                soundings.add(new SoundingOb(s, dt));
525                if ( !times.contains(dt)) {
526                    times.add(dt);
527                }
528            } catch (Exception vexcp) {
529                LogUtil.logException("Creating sounding", vexcp);
530            }
531        }
532        Collections.sort(times);
533        if (debug) {
534            System.out.println("Times:" + times);
535        }
536    }
537
538    /**
539     *  Set the ADDE server name
540     *
541     * @param  server  server name or IP address
542     */
543    public void setSource(String server) {
544        this.server = server;
545        if (serverProperty != null) {
546            serverProperty.setValue(server);
547        }
548    }
549
550    /**
551     * Get the source of the data (server)
552     *
553     * @return  server name or IP address
554     */
555    public String getSource() {
556        return server;
557    }
558
559
560    /**
561     * Set the mandatory data set name
562     *
563     * @param value  mandatory data set name
564     */
565    public void setMandDataset(String value) {
566        mandDataset = value;
567    }
568
569    /**
570     * Set the mandatory data set name
571     *
572     * @return  the mandatory data set name
573     */
574    public String getMandDataset() {
575        return mandDataset;
576    }
577
578
579    /**
580     * Set the significant data set name
581     *
582     * @param value the significant data set name
583     */
584    public void setSigDataset(String value) {
585        sigDataset = value;
586    }
587
588    /**
589     * Get the significant data set name
590     *
591     * @return the significant data set name
592     */
593    public String getSigDataset() {
594        return sigDataset;
595    }
596
597    
598    /**
599     * Change behavior if we are looking at satellite soundings
600     */
601    public void setSatelliteSounding(boolean flag) {
602        satelliteSounding = flag;
603    }
604
605    /**
606     * Are we looking at satellite soundings?
607     */
608    public boolean getSatelliteSounding() {
609        return satelliteSounding;
610    }
611
612
613    /**
614     * Check to see if the RAOB has any data
615     *
616     * @param sound    sounding to check
617     * @return  a sounding with data
618     */
619    public SoundingOb initSoundingOb(SoundingOb sound) {
620        if ( !sound.hasData()) {
621            setRAOBData(sound);
622        }
623        return sound;
624    }
625
626    /**
627     * Make the select string that will get this observation
628     *
629     * @param sound   sounding to use
630     * @return  select string
631     */
632    private String makeSelectString(SoundingOb sound) {
633        return makeSelectString(sound.getStation().getIdentifier(),
634                                sound.getTimestamp());
635    }
636
637
638    /**
639     * Make a select string for the given station id and date
640     *
641     * @param wmoId    station id
642     * @param date     time of data
643     * @return  ADDE select clause for the given parameters
644     */
645    private String makeSelectString(String wmoId, DateTime date) {
646        String day  = UtcDate.getYMD(date);
647        String time = UtcDate.getHHMM(date);
648        //int[] daytime = McIDASUtil.mcSecsToDayTime((long) date.getValue());
649        return new String(idVar + " " + wmoId + ";" + dayVar + " " + day
650                          + ";" + timeVar + " " + time);
651    }
652
653    /**
654     * Fills in the data for the RAOB
655     *
656     * @param sound   sounding ob to set
657     */
658    private void setRAOBData(SoundingOb sound) {
659
660        initGroupAndDescriptors();
661        int                 numLevels;
662        Unit                pUnit   = null,
663                            tUnit   = null,
664                            tdUnit  = null,
665                            spdUnit = null,
666                            dirUnit = null,
667                            zUnit   = null;
668        float               p[], t[], td[], z[], spd[], dir[];
669        AddePointDataReader apdr;
670
671        String              request = getMandatoryURL(sound);
672
673        dbPrint(request);
674        try {
675            if (sound.getMandatoryFile() != null) {
676                request = "file:" + sound.getMandatoryFile();
677                //                System.err.println ("using fixed mandatory url:" + request);
678            }
679
680            apdr = new AddePointDataReader(request);
681            String[] params = apdr.getParams();
682            int[]    scales = apdr.getScales();
683            String[] units  = apdr.getUnits();
684            int[][]  data   = apdr.getData();
685
686            // Special case: GRET doesn't respond to SELECT DAY...
687            // Try again without it
688            if (satelliteSounding && data[0].length == 0) {
689                request = request.replaceAll("DAY [0-9-]+;?", "");
690                apdr = new AddePointDataReader(request);
691                params = apdr.getParams();
692                scales = apdr.getScales();
693                units  = apdr.getUnits();
694                data   = apdr.getData();
695            }
696            
697            numLevels = data[0].length;
698            if (numLevels > 0) {
699                dbPrint("Num mand pressure levels = " + numLevels);
700                // Get the their units
701                pUnit = getUnit(units[0]);
702                // NB: geopotential altitudes stored in units of length
703                zUnit = GeopotentialAltitude.getGeopotentialUnit(
704                    getUnit(units[1]));
705                tUnit   = getUnit(units[2]);
706                tdUnit  = getUnit(units[3]);
707                // Satellite soundings don't have spd or dir
708                if (units.length > 4) {
709                        spdUnit = getUnit(units[4]);
710                        dirUnit = getUnit(units[5]);
711                }
712                else {
713                        spdUnit = getUnit("MPS");
714                        dirUnit = getUnit("DEG");
715                }
716                
717                // initialize the arrays
718                p   = new float[numLevels];
719                z   = new float[numLevels];
720                t   = new float[numLevels];
721                td  = new float[numLevels];
722                spd = new float[numLevels];
723                dir = new float[numLevels];
724
725                // fill the arrays
726                for (int i = 0; i < numLevels; i++) {
727                    p[i]   = (float) scaleValue(data[0][i], scales[0]);
728                    z[i]   = (float) scaleValue(data[1][i], scales[1]);
729                    t[i]   = (float) scaleValue(data[2][i], scales[2]);
730                    td[i]  = (float) scaleValue(data[3][i], scales[3]);
731                    // Satellite soundings don't have spd or dir
732                    if (data.length > 4 && scales.length > 4) {
733                        spd[i] = (float) scaleValue(data[4][i], scales[4]);
734                        dir[i] = (float) scaleValue(data[5][i], scales[5]);
735                    }
736                    else {
737                        if (i==0) spd[i] = dir[i] = (float) 0;
738                        else spd[i] = dir[i] = (float) scaleValue(McIDASUtil.MCMISSING, 0);
739                    }
740                }
741                if (debug) {
742                    System.out.println("P[" + pUnit + "]\t" + "Z[" + zUnit
743                                       + "]\t" + "T[" + tUnit + "]\t" + "TD["
744                                       + tdUnit + "]\t" + "SPD[" + spdUnit
745                                       + "]\t" + "DIR[" + dirUnit + "]");
746                    for (int i = 0; i < numLevels; i++) {
747                        System.out.println(p[i] + "\t" + z[i] + "\t" + t[i]
748                                           + "\t" + td[i] + "\t" + spd[i]
749                                           + "\t" + dir[i]);
750                    }
751                }
752                sound.getRAOB().setMandatoryPressureProfile(pUnit, p, tUnit,
753                        t, tdUnit, td, spdUnit, spd, dirUnit, dir, zUnit, z);
754            }
755        } catch (Exception e) {
756            LogUtil.logException(
757                "Unable to set mandatory pressure data for station "
758                + sound.getStation(), e);
759        }
760
761        // Done if we have no sig data
762        if ((sigGroup == null) || (sigDescriptor == null)) {
763            return;
764        }
765
766        request = getSigURL(sound);
767        dbPrint(request);
768
769        // get the sig data
770        try {
771            if (sound.getSigFile() != null) {
772                request = "file:" + sound.getSigFile();
773                //                System.err.println ("using fixed sig url:" + request);
774            }
775
776            apdr = new AddePointDataReader(request);
777            String[] params = apdr.getParams();
778            int[]    scales = apdr.getScales();
779            String[] units  = apdr.getUnits();
780            int[][]  data   = apdr.getData();
781
782            numLevels = data[0].length;
783            if (numLevels > 0) {
784                // Determine how many of each kind of level
785                int numSigW = 0;
786                int numSigT = 0;
787                for (int i = 0; i < data[0].length; i++) {
788                    if (intEqual(data[0][i], "SIGT")) {
789                        numSigT++;
790                    }
791                    if (intEqual(data[0][i], "SIGW")) {
792                        numSigW++;
793                    }
794                }
795
796                dbPrint("Num sig temperature levels = " + numSigT);
797                dbPrint("Num sig wind levels = " + numSigW);
798
799
800                // Get the units & initialize the arrays
801                pUnit  = getUnit("mb");
802                tUnit  = getUnit("k");
803                tdUnit = getUnit("k");
804                // NB: geopotential altitudes stored in units of length
805                zUnit =
806                    GeopotentialAltitude.getGeopotentialUnit(getUnit("m"));
807                spdUnit = getUnit("mps");
808                dirUnit = getUnit("deg");
809
810                p       = new float[numSigT];
811                t       = new float[numSigT];
812                td      = new float[numSigT];
813                z       = new float[numSigW];
814                spd     = new float[numSigW];
815                dir     = new float[numSigW];
816
817                // fill the arrays
818                int j = 0;  // counter for sigT
819                int l = 0;  // counter for sigW
820                for (int i = 0; i < numLevels; i++) {
821                    if (intEqual(data[0][i], "SIGT")) {
822                        p[j]  = (float) scaleValue(data[3][i], 1);
823                        t[j]  = (float) scaleValue(data[1][i], 2);
824                        td[j] = (float) scaleValue(data[2][i], 2);
825                        j++;
826                    } else if (intEqual(data[0][i], "SIGW")) {
827                        z[l] = (data[3][i] == 0)
828                               ? (float) ((SoundingStation) sound
829                                   .getStation()).getAltitudeAsDouble()
830                               : (float) scaleValue(data[3][i], 0);
831                        spd[l] = (float) scaleValue(data[2][i], 1);
832                        dir[l] = (float) scaleValue(data[1][i], 0);
833                        l++;
834                    }
835                }
836                if (numSigT > 0) {
837                    try {
838                        if (debug) {
839                            System.out.println("P[" + pUnit + "]\tT[" + tUnit
840                                    + "]\tTD[" + tdUnit + "]");
841                            for (int i = 0; i < numSigT; i++) {
842                                System.out.println(p[i] + "\t" + t[i] + "\t"
843                                        + td[i]);
844                            }
845                        }
846                        sound.getRAOB().setSignificantTemperatureProfile(
847                            pUnit, p, tUnit, t, tdUnit, td);
848                    } catch (Exception e) {
849                        LogUtil.logException(
850                            "Unable to set significant temperature data for station "
851                            + sound.getStation(), e);
852                    }
853                }
854                if (numSigW > 0) {
855                    try {
856                        if (debug) {
857                            System.out.println("Z[" + zUnit + "]\tSPD["
858                                    + spdUnit + "]\tDIR[" + dirUnit + "]");
859                            for (int i = 0; i < numSigW; i++) {
860                                System.out.println(z[i] + "\t" + spd[i]
861                                        + "\t" + dir[i]);
862                            }
863                        }
864                        sound.getRAOB().setSignificantWindProfile(zUnit, z,
865                                spdUnit, spd, dirUnit, dir);
866                    } catch (Exception e) {
867                        LogUtil.logException(
868                            "Unable to set significant wind data for station "
869                            + sound.getStation(), e);
870                    }
871                }
872            }
873        } catch (Exception e) {
874            LogUtil.logException(
875                "Unable to retrieve significant level data for station "
876                + sound.getStation(), e);
877        }
878    }
879
880
881    /**
882     * scale the values returned from the server
883     *
884     * @param value    value to scale
885     * @param scale    scale factor
886     * @return   scaled value
887     */
888    private double scaleValue(int value, int scale) {
889        return (value == McIDASUtil.MCMISSING)
890               ? Double.NaN
891               : (value / Math.pow(10.0, (double) scale));
892    }
893
894    /**
895     * Gets the units of the variable.  Now just a passthrough to
896     * ucar.visad.Util.
897     *
898     * @param unitName   unit name
899     * @return  corresponding Unit or null if can't be decoded
900     * @see ucar.visad.Util#parseUnit(String)
901     */
902    private Unit getUnit(String unitName) {
903        try {
904            return Util.parseUnit(unitName);
905        } catch (Exception e) {}
906        return null;
907    }
908
909    /**
910     * Get a default value  using this Adapter's prefix
911     *
912     * @param name  name of property key
913     * @param dflt  default value
914     * @return  the default for that property or dflt if not in properties
915     */
916    private String getDflt(String name, String dflt) {
917        return getDflt("AddeSoundingAdapter.", name, dflt);
918    }
919
920    /**
921     * Determines the names of the variables in the netCDF file that
922     * should be used.
923     */
924    private void getVariables() {
925        // initialize the defaults for this object
926        try {
927            defaultServer      = getDflt("serverName", defaultServer);
928            defaultMandDataset = getDflt("mandDataset", defaultMandDataset);
929            defaultSigDataset  = getDflt("sigDataset", defaultSigDataset);
930            idVar              = getDflt("stationIDVariable", idVar);
931            latVar             = getDflt("latitudeVariable", latVar);
932            lonVar             = getDflt("longitudeVariable", lonVar);
933            eleVar             = getDflt("stationElevVariable", eleVar);
934            timeVar            = getDflt("soundingTimeVariable", timeVar);
935            dayVar             = getDflt("soundingDayVariable", dayVar);
936
937            prMandPVar         = getDflt("mandPPressureVariable", prMandPVar);
938            htMandPVar         = getDflt("mandPHeightVariable", htMandPVar);
939            tpMandPVar         = getDflt("mandPTempVariable", tpMandPVar);
940            tdMandPVar         = getDflt("mandPDewptVariable", tdMandPVar);
941            spdMandPVar        = getDflt("mandPWindSpeedVariable", spdMandPVar);
942            dirMandPVar        = getDflt("mandPWindDirVariable", dirMandPVar);
943
944            // Significant Temperature data
945            /*
946              numSigT      = nc.get(getDflt("NetcdfSoundingAdapter.", "numSigTempLevels", "numSigT"));
947              if (numSigT != null)    {
948              hasSigT = true;
949              prSigTVar =  getDflt ("NetcdfSoundingAdapter.", "sigTPressureVariable", "prSigT");
950              tpSigTVar =  getDflt ("NetcdfSoundingAdapter.", "sigTTempVariable", "tpSigT");
951              tdSigTVar =  getDflt("NetcdfSoundingAdapter.", "sigTDewptVariable", "tdSigT");
952              }
953
954              // Significant Wind data
955              numSigW =  nc.get(getDflt("NetcdfSoundingAdapter.", "numSigWindLevels", "numSigW"));
956              if (numSigW != null)  {
957              hasSigW = true;
958              htSigWVar =  getDflt ("NetcdfSoundingAdapter.", "sigWHeightVariable", "htSigW");
959              spdSigWVar = getDflt("NetcdfSoundingAdapter.", "sigWWindSpeedVariable", "wsSigW");
960              dirSigWVar = getDflt("NetcdfSoundingAdapter.", "sigWWindDirVariable", "wdSigW");
961              }
962            */
963        } catch (Exception e) {
964            System.out.println("Unable to initialize defaults file");
965        }
966    }
967
968    /**
969     * Get significant data ADDE user/project id for the data
970     * @return  user/project string (ex: "id=idv proj=0")
971     */
972    private String getSigUserProj() {
973        return getUserProj(new String(sigGroup + "/"
974                                      + sigDescriptor).toUpperCase());
975    }
976
977    /**
978     * Make the mandatory levels URL for the given sounding
979     *
980     * @param sound sounding
981     *
982     * @return mandatory url
983     */
984    public String getMandatoryURL(SoundingOb sound) {
985        String select      = makeSelectString(sound);
986        String paramString;
987        if (!satelliteSounding) {
988                paramString = StringUtil.join(new String[] {
989                    prMandPVar, htMandPVar, tpMandPVar, tdMandPVar, spdMandPVar, dirMandPVar
990                });
991        }
992        else {
993                paramString = StringUtil.join(new String[] {
994                prMandPVar, htMandPVar, tpMandPVar, tdMandPVar
995            });
996        }
997        String request = makeUrl(new String[] {
998            P_GROUP, manGroup, P_DESCR, manDescriptor, P_SELECT,
999            sQuote(select), P_PARAM, paramString, P_NUM, P_ALL, P_POS, P_ALL
1000        }) + getManUserProj();
1001
1002        return request;
1003
1004    }
1005
1006    /**
1007     * Make the url for the significant levels for the sounding
1008     *
1009     * @param sound the sounding
1010     *
1011     * @return sig url
1012     */
1013    public String getSigURL(SoundingOb sound) {
1014        // If we haven't picked a sig dataset, act as though both are mandatory
1015        if (mandDataset.equals(sigDataset)) {
1016                return getMandatoryURL(sound);
1017        }
1018
1019        String select      = makeSelectString(sound);
1020        String paramString;
1021        if (!satelliteSounding) {
1022                paramString = "type p1 p2 p3";
1023        }
1024        else {
1025                paramString = StringUtil.join(new String[] {
1026                prMandPVar, htMandPVar, tpMandPVar, tdMandPVar
1027            });
1028        }
1029        String request = makeUrl(new String[] {
1030            P_GROUP, sigGroup, P_DESCR, sigDescriptor, P_SELECT,
1031            sQuote(select), P_PARAM, paramString, P_NUM, P_ALL, P_POS, P_ALL
1032        })  + getSigUserProj();
1033        
1034        return request;
1035    }
1036
1037
1038    /**
1039     * Get mandatory data ADDE user/project id for the data
1040     *
1041     * @return  user/project string (ex: "id=idv proj=0")
1042     */
1043    private String getManUserProj() {
1044        return getUserProj(new String(manGroup + "/"
1045                                      + manDescriptor).toUpperCase());
1046    }
1047
1048    /**
1049     * Get the user/project string for the given key
1050     *
1051     * @param key   group/descriptor
1052     *
1053     * @return  user/project string (ex: "id=idv proj=0")  for the specified
1054     *          dataset
1055     */
1056    private String getUserProj(String key) {
1057        StringBuffer buf = new StringBuffer();
1058        buf.append("&proj=");
1059        buf.append(getDflt("", key.toUpperCase().trim() + ".proj", proj));
1060        buf.append("&user=");
1061        buf.append(getDflt("", key.toUpperCase().trim() + ".user", user));
1062        buf.append("&compress=gzip");
1063        //buf.append("&debug=true");
1064        return buf.toString();
1065    }
1066
1067    /**
1068     * Get the select string for use in loadStations
1069     *
1070     * @return  select string
1071     */
1072    private String getStationsSelectString() {
1073        StringBuffer buf;
1074        if (!satelliteSounding) {
1075                if ( !mainHours) {
1076                        return "";
1077                }
1078                buf = new StringBuffer();
1079                buf.append("&SELECT='");
1080                buf.append(timeVar + " 00,12'");
1081        }
1082        else {
1083                buf = new StringBuffer();
1084                buf.append("&SELECT='");
1085                buf.append(timeVar + " " + satelliteTime);
1086                if (!satellitePixel.equals("")) {
1087                        buf.append("; " + idVar + " " + satellitePixel);
1088                }
1089                buf.append("'");
1090        }
1091        return buf.toString();
1092    }
1093
1094    /**
1095     * test by running java ucar.unidata.data.sounding.AddeSoundingAdapter
1096     *
1097     * @param args   array of arguments.  Takes up to 3 arguments as
1098     *               "server&nbsp;mandatory dataset&nbsp;significant dataset"
1099     *               Use "x" for any of these arguments to use the default.
1100     */
1101    public static void main(String[] args) {
1102        String server = "adde.unidata.ucar.edu";
1103        String manset = "rtptsrc/uppermand";
1104        String sigset = "rtptsrc/uppersig";
1105        if (args.length > 0) {
1106            server = ( !(args[0].equalsIgnoreCase("x")))
1107                     ? args[0]
1108                     : server;
1109            if (args.length > 1) {
1110                manset = ( !(args[1].equalsIgnoreCase("x")))
1111                         ? args[1]
1112                         : manset;
1113            }
1114            if (args.length > 2) {
1115                sigset = ( !(args[2].equalsIgnoreCase("x")))
1116                         ? args[2]
1117                         : sigset;
1118            }
1119        }
1120        //        try  {
1121        //            AddeSoundingAdapter asa = 
1122        //                //new AddeSoundingAdapter(server, manset, sigset);
1123        //                new AddeSoundingAdapter();
1124        /*
1125          Thread.sleep(5000);
1126          asa.setServer("hurri.kean.edu");
1127          Thread.sleep(5000);
1128          asa.setServer("adde.unidata.ucar.edu");
1129          Thread.sleep(5000);
1130          asa.setMandatoryDataset("blizzard/uppermand");
1131        */
1132        //        }
1133        //        catch (Exception me)   {
1134        //            System.out.println(me);
1135        //        }
1136    }
1137
1138
1139    /**
1140     * The string representation
1141     * @return The string
1142     */
1143    public String toString() {
1144        return "SoundingAdapter:" + server;
1145
1146    }
1147
1148
1149
1150
1151
1152}
1153