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