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