[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

20030801: IDV 2D Display only on Linux?



To: address@hidden
From: "Todd Plessel" <address@hidden>
Subject: IDV 2D Display only on Linux?
Organization: EPA
Keywords: 200308010054.h710sOLd023456 IDV Linux

Hi Todd-

Hope you're back from a wonderful vacation.

It was tough to come back. ;-)

Now about IDV:
I noticed that when viewing a 3D Vis5D file on a Linux host
that IDV uses a 2D display instead of a 3D display which
precludes 3D visualizations such as isosurfaces.

Are you running this from a local build or from the
InstallAnywhere version?  If local, do you have Java 3D installed?
I just tried with the InstallAnywhere version and it worked
fine.

Likewise, when a 1 layer grid is read in there are no Fields (e.g., 2D)
listed so no displays can be made.

Hmm, that is odd.  For the file I tried, it listed the 2D fields.
Could you send a sample file?

Any progress on figuring-out why the 3D grid appears below
the map surface and why the Color Shaded Plan View doesn't appear?

I have not looked at this since I left.  I'm still catching up
and Stu's out sick today.  I'll have to review your previous
e-mail and look into this.

Where you able to display those sample data files (profiler, raob, etc.)?

Normally, the point data (madis, metar, maritime) files would be read
in by the ucar.unidata.data.point.NetcdfMetarDataSource (not a good
name, but was first only for Metar).  There are a couple
of problems with these files.  First there was some ill concieved
logic in NetcdfMetarDataSource for picking out the observation data
that are indexed on recnum.  The other problem is with the date variable.
NetcdfMetarDataSource has a list of possible time variables to
use and none of the ones in the files were in the list.  Adding
those revealed the next problem, namely that the unit for time
is invalid (seconds since 1-1-1970).  This does not conform to
the ISO standard and really says seconds since year 1, month 1,
the 1970th day.  I put a hack into NetcdfMetarDataSource which
will assume seconds since 1970-1-1. I've attached a version
which should allow you to read in all the point files.  You'll
need to create your own station models for the parameters you
want to view (Edit/Station models menu) or add the specific parameters
to the parameter aliases that are used in the default models
(Edit/Parameter aliases menu).

I was able to display skew-Ts from the RAOB file using the file
selector under the RAOB tab.  However, the times being used are the
release times and it's not handling the ones with a fill
value very well.  I'm doing some work on the sounding adapter
anyway, so will fix this as well.  Actually, I've attached a
version that fixes the problem. I've changed it to look for
the global attribute "timeVariables" to figure out what
time var to use and default to the old one.  It also will
handle units on the times (defaulting to seconds since
the epoch).  Some of the obs are bad and there are nasty
error messages that pop up when you try to display these.
I'll work on making that a little nicer as I continue working
on the sounding adapter.

If you want plan view plots of soundings, that will require
a new adapter.   We need to do that in the future, but it's
not on our immediate radar screen.

For the profiler data, you would need to write a new adapter.
Currently, we only plot profiler data from an ADDE server.
In GEMPAK and McIDAS, we decode these netCDF files into
their formats and in the process, "normalize" the data so
that all heights are MSL instead of AGL, throw out missing
values, etc.  So, we opted to take the easy route and get
the processed data from an ADDE server.

Do I need to write a Convention or an Adapter for these NetCDF files
or do they already exist?

Please let me know any progress so I can report on it.

Send me the vis5d file and I'll get back to you next week
on your other issue.

Don
*************************************************************
Don Murray                               UCAR Unidata Program
address@hidden                        P.O. Box 3000
(303) 497-8628                              Boulder, CO 80307
http://www.unidata.ucar.edu/staff/donm
*************************************************************

// $Id: NetcdfMetarDataSource.java,v 1.3 2002/10/29 00:25:48 jeffmc Exp $
/*
 * Copyright 1997-2001 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * address@hidden.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package ucar.unidata.data.point;

import java.util.Hashtable;

import ucar.unidata.data.*;

import ucar.unidata.util.Misc;
import ucar.unidata.util.LogUtil;

import java.rmi.RemoteException;
import edu.wisc.ssec.mcidas.McIDASUtil;

import visad.*;
import visad.georef.EarthLocation;
import visad.georef.EarthLocationTuple;
import visad.data.netcdf.Plain;

import org.apache.log4j.Category;

/**
 * A data source for ADDE point data
 *
 * @author Don Murray
 * @version $Revision: 1.3 $ $Date: 2002/10/29 00:25:48 $
 */
public class NetcdfMetarDataSource extends DataSourceImpl {


    static Category log_ = 
       Category.getInstance (NetcdfMetarDataSource.class.getName());

    private String  source;
    private String[] timeVars = {"time_nominal", 
                                 "time_Nominal",
                                 "timeNominal",
                                 "timeObs",
                                 "reportTime",
                                 "time"};

    public NetcdfMetarDataSource () 
        throws VisADException {
        init ();
    }

    public NetcdfMetarDataSource(DataSourceDescriptor descriptor, String 
source, Hashtable properties) 
       throws VisADException {
        super (descriptor,  source, "Netcdf Metar Data", properties);
        this.source = source;
        try {
            init ();
        } catch (VisADException exc) {
            setInError (true);
            throw exc;
        }
    } 

    private void init () 
        throws VisADException {
    }

    public void doMakeDataChoices() {
       DataChoice choice = 
           new DirectDataChoice (this, source, "Station Model Plot", "Station 
Data Plot", 
               DataCategory.parseCategories 
(DataCategory.CATEGORY_POINT+";StationPlot", false), null);
        addDataChoice(choice);
    }

    protected Data getDataInner (DataChoice dataChoice, DataCategory category, 
                         DataSelection dataSelection) 
        throws VisADException, RemoteException 
    {
        try {
           return makeObs(dataChoice, dataSelection);
        } catch (Exception exc) {
            logException ("Creating obs", exc);
        }
        return null;
    }

    private FieldImpl makeObs (DataChoice dataChoice, DataSelection subset) 
        throws Exception {
        String source = (String) dataChoice.getId ();
//        FieldImpl obs = (FieldImpl) getCache (source);
        FieldImpl obs = null;
        if (obs == null) {
            Plain plain = new Plain(true); // true = convert char to Text
            obs = makePointObs(plain.open(source));
            //putCache (source, obs);
        }
        return obs;
    }

    /**
     * Check to see if this NetcdfMetarDataSource is equal to the object
     * in question.
     * @param o  object in question
     * @return true if they are the same or equivalent objects
     */
    public boolean equals(Object o) {
        if (!(o instanceof NetcdfMetarDataSource)) return false;
        NetcdfMetarDataSource that = (NetcdfMetarDataSource) o;
        return (this == that);
    }

    public int hashCode() {
        int hashCode = getName().hashCode();
        return hashCode;
    }

  /**
   * Take a field of data and turn it into a field of PointObs.  Right
   * now, this assumes a surface data from an metar2nc type netCDF file
   * or similar (i.e. AWIPS).  We use the FieldImpl that has domain
   * recNum().
   * @return field of PointObs
   */
  private FieldImpl makePointObs(Data input) 
    throws VisADException {

    long millis = System.currentTimeMillis();
    FieldImpl retField = null;
    try {
      // first check to see if we can make a location ob
      // input has to have a FieldImpl as one component of the 
      // form (recNum -> (parm1, parm2, parm3, ...., parmN))

      FieldImpl recNumObs = null;
      RealType recNum = RealType.getRealType("recNum");
      MathType inputType = input.getType();
      if (input instanceof Tuple) {
        TupleType tt = (TupleType) input.getType();
        for (int i = 0; i < tt.getDimension(); i++) {
            MathType compType = tt.getComponent(i);
            if ((compType instanceof FunctionType)) {
                RealTupleType domType = ((FunctionType)compType).getDomain();
                if (domType.getDimension() == 1 &&
                    recNum.equals(domType.getComponent(0))) {
                    recNumObs = (FieldImpl) ((Tuple) input).getComponent(i);
                    break;
                }
            }
        }
      } else if ((inputType instanceof FunctionType) && 
                 (recNum.equals(((FunctionType)inputType).getDomain()))) {
            recNumObs = (FieldImpl) input;
      }
      if (recNumObs == null) {
        throw new IllegalArgumentException(
            "don't know how to convert input to a point ob");
      }
      TupleType type;
      Gridded1DSet indexSet = null;
      try {
        type = (TupleType) ((FunctionType)recNumObs.getType()).getRange();
        indexSet = (Gridded1DSet) recNumObs.getDomainSet();
      }
      catch (ClassCastException ce) {
        throw new IllegalArgumentException(
            "don't know how to convert input to a point ob");
      }
      //System.out.println(indexSet.getLength() + " obs");
      long mil2 = System.currentTimeMillis();
      boolean allReals = (type instanceof RealTupleType);
  
      // check for time 
      int timeIndex = -1;
      for (int i = 0; i < timeVars.length; i++) {
         timeIndex =  type.getIndex(timeVars[i]);
         if (timeIndex > -1) break;
      }

      if (timeIndex == -1) {
        throw new IllegalArgumentException("can't find DateTime components");
      }
  
      // Check for LAT/LON/ALT
      int latIndex = type.getIndex(RealType.Latitude);
      int lonIndex = type.getIndex(RealType.Longitude);
      int altIndex = type.getIndex(RealType.Altitude);
      //if (altIndex == -1) altIndex = type.getIndex("elev");
      if (latIndex == -1 || lonIndex == -1) {
        throw new IllegalArgumentException("can't find lat/lon");
      }
      
      int[] indicies = new int[] {timeIndex, latIndex, 
                                  lonIndex, altIndex};
  
      int numVars = type.getDimension();
      int numNotRequired = numVars - ((altIndex != -1)?4:3);
      //System.out.println("Of " + numVars + " vars, " + numNotRequired + 
      //                   " are not required");
  
      int[] notReqIndices = new int[numNotRequired];
  
      int l = 0;
      for (int i = 0; i < numVars; i++) {
        if (i != timeIndex && i != latIndex &&
            i != lonIndex && i != altIndex ) {
          notReqIndices[l++] = i;
        }
      }
      //System.out.println("Setup took " + (System.currentTimeMillis() - mil2));
  
      PointOb[] obs = new PointObTuple[indexSet.getLength()];
      for (int i = 0; i < indexSet.getLength(); i++) {
        mil2 = System.currentTimeMillis();
        Tuple ob = (Tuple) recNumObs.getSample(i);
        // get location
        EarthLocation location = 
          new EarthLocationTuple( 
            (Real) ob.getComponent(latIndex),
            (Real) ob.getComponent(lonIndex),
            (altIndex != -1)
              ? (Real) ob.getComponent(altIndex)
              : new Real(RealType.Altitude, 0));
        /*
        // get DateTime
        DateTime dateTime = new DateTime((Real)ob.getComponent(timeIndex));
        */
        // get DateTime.  Must have valid time unit.  If not assume
        // seconds since epoch.  Maybe we should throw an error?
        Real timeVal = (Real)ob.getComponent(timeIndex);
        DateTime dateTime = null;
        if (timeVal.getUnit() != null) {
            dateTime = new DateTime(timeVal);
        } else {  // assume seconds since epoch
            dateTime = new DateTime(timeVal.getValue());
        }
        // now make data
        Data[] others = (allReals == true) 
                                 ? new Real[numNotRequired]
                                 : new Data[numNotRequired];
        for (int j = 0; j < numNotRequired; j++) {
            others[j] = (allReals == true) 
                              ? (Real) ob.getComponent(notReqIndices[j])
                              : (Data) ob.getComponent(notReqIndices[j]);
        }          
        Data rest = (allReals == true) 
                       ? new RealTuple((Real[]) others)
                       : new Tuple(others, false);
        //obs[i] = new PointObField(location, dateTime, rest);
        obs[i] = new PointObTuple(location, dateTime, rest);
        //System.out.println("made data in " + (System.currentTimeMillis() - 
mil2));
      }
      retField = 
        new FieldImpl(
          new FunctionType(((SetType)indexSet.getType()).getDomain(), 
                           obs[0].getType()), indexSet);
      retField.setSamples(obs, false);

    } catch (RemoteException re) {
      throw new VisADException("got RemoteException " + re);
    }
    //System.out.println("Making point obs took " + (System.currentTimeMillis() 
- millis));
    return retField;
  }


    public void setSource (String value) {
        source = value;
    }
    public String getSource () {
        return source;
    }

}
// $Id: NetcdfSoundingAdapter.java,v 1.8 2002/08/30 22:31:34 jeffmc Exp $
/*
 * Copyright 1997-2000 Unidata Program Center/University Corporation for
 * Atmospheric Research, P.O. Box 3000, Boulder, CO 80307,
 * address@hidden.
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or (at
 * your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */
package ucar.unidata.data.sounding;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
import ucar.netcdf.*;
import ucar.unidata.metdata.Station;

import ucar.unidata.data.sounding.RAOB;
import ucar.unidata.data.sounding.SoundingOb;
import ucar.unidata.data.sounding.SoundingStation;
import ucar.unidata.util.Defaults;
import ucar.visad.quantities.GeopotentialAltitude;
import visad.*;
import visad.data.units.Parser;

/**
 * This class creates VisAD compatible data objects from a netCDF
 * file of upper air soundings.  
 *
 * @author  Don Murray, Unidata/UCAR
 */
public class NetcdfSoundingAdapter  extends SoundingAdapterImpl
    implements SoundingAdapter
{

    private String filename;

    private NetcdfFile nc = null;


    private int numStations;            // number of stations in file

    private String prMandPVar;          // name of mandP pressure variable
    private String htMandPVar;          // name of mandP height variable
    private String tpMandPVar;          // name of mandP temp variable
    private String tdMandPVar;          // name of mandP dewpt variable
    private String spdMandPVar;         // name of mandP wind speed variable
    private String dirMandPVar;         // name of mandP wind dir variable

    private String htMandWVar;          // name of mandW height variable
    private String spdMandWVar;         // name of mandW wind speed variable
    private String dirMandWVar;         // name of mandW wind dir variable

    private String prSigTVar;          // name of sig T pressure variable
    private String tpSigTVar;          // name of sig T temp variable
    private String tdSigTVar;          // name of sig T dewpt variable

    private String htSigWVar;          // name of sig wind height variable
    private String spdSigWVar;         // name of sig wind wind speed variable
    private String dirSigWVar;         // name of sig wind wind dir variable

    private String prMaxWVar;          // name of max wind pressure variable
    private String spdMaxWVar;         // name of max wind wind speed variable
    private String dirMaxWVar;         // name of max wind wind dir variable

    private String prTropVar;          // name of tropopause pressure variable
    private String tpTropVar;          // name of tropopause temp variable
    private String tdTropVar;          // name of tropopause dewpt variable
    private String spdTropVar;         // name of tropopause wind speed variable
    private String dirTropVar;         // name of tropopause wind dir variable

    private Variable stid;              // station id 
    private Variable lat;               // station latitude 
    private Variable lon;               // station longitude
    private Variable elev;              // station elevation
    private Variable time;              // sounding time
    private Variable numMandP;          // number mandatory pressure levels
    private Variable numMandW;          // number mandatory wind levels
    private Variable numSigT;           // number significant temp levels
    private Variable numSigW;           // number of significant wind levels
    private Variable numMaxW;           // number of max wind levels
    private Variable numTrop;           // number of tropopause levels

    private boolean hasMandP = false;   // file has mandatory pressure data
    private boolean hasMandW = false;   // file has mandatory wind data
    private boolean hasSigT  = false;   // file has significant temp data
    private boolean hasSigW  = false;   // file has significant wind data
    private boolean hasMaxW  = false;   // file has max wind data
    private boolean hasTrop  = false;   // file has tropopause data
    private boolean oneTrop  = false;   // file has tropopause data in 
    //     old format (only one)
    private boolean 
        dewpointIsDepression = true;   // file stores dewpoint as value
    private float missingValue = 99999.f;  // default missing value
    private Unit timeUnit = null;
    private double timeFill = Double.NaN;

    private SoundingStation s;

  
    private File myFile;

    /**
       ctor for reflection based construction
    **/
    public NetcdfSoundingAdapter() {
        super ("NetcdfSoundingAdapter");
    }


    /**
     * Read a netCDF file of decoded soundings.
     *
     * @param  filename  the fully qualified path and name of the file 
     *                   to be adapted.
     */
    public NetcdfSoundingAdapter  (String filename)         throws Exception {
        this  (new File (filename));
    }
      

    public NetcdfSoundingAdapter (File file) 
        throws Exception {
        super ("NetcdfSoundingAdapter");
        myFile = file;
        filename = myFile.getAbsolutePath();
        init ();
    }

    public void update () {
        try {
            haveInitialized = false;
            init ();
        } catch (Exception exc) {
            logException ("Doing update", exc);
        }
    }



    /**
     * Read a netCDF file of decoded soundings.
     *
     * @param  filename  the file to be adapted.
     */
    protected void init ()         throws Exception {
        if (haveInitialized) return;
        super.init ();

        try {
            nc = new NetcdfFile(myFile, true);

            // get the names of the variables to be used.
            getVariables ();
    
            // get the station list and number of stations
            numStations = stid.getLengths()[0];
            stations = new ArrayList (numStations);  // array of stations
            soundings = new ArrayList (numStations); // array of soundings
            times = new ArrayList (10);
            int index[] = new int[1];    // index array to specify which value

            // fill the station and sounding lists
            for (int i=0; i<numStations; i++) {
                index[0] = i;

                DateTime sndTime;
                // Set the station (s)
                try  {
                    makeSoundingStation(index);
                    sndTime = getObsTime(index);
                } catch (Exception e)  {
                    continue;
                }

                // Set the data
                if (sndTime != null) makeSoundingOb(index, s, sndTime);
            }
            Collections.sort(times);
        }  catch (Exception ne) {
            logException ("Unable to read upper air netCDF file", ne);
        }
      
    }
  
    public String getSource () {
        return filename;
    }

    public  void setSource (String s) {
        filename = s;
        myFile  =new File (s);
    }


    /** check to see if the RAOB has any data */
    public SoundingOb initSoundingOb (SoundingOb sound)  {
        checkInit ();
        if (!sound.hasData ()) {
            int idx = soundings.indexOf (sound);
            if (idx <0) {
                throw new IllegalArgumentException ("SoundingAdapter does not 
contain sounding:" + sound);
            }
            setRAOBData (new int[] {idx}, sound);
        }
        return sound;
    }



    /** create a sounding station object from the netCDF file info */
    private void makeSoundingStation(int[] index) 
        throws Exception {
        String wmoID;
        double latvalue;
        double lonvalue;
        double elevvalue;
        try {
            wmoID = Integer.toString(stid.getInt(index));
            latvalue = lat.getDouble(index);
            lonvalue = lon.getDouble(index);
            elevvalue = elev.getDouble(index);
        }  catch (Exception ne) {
            throw new Exception(ne.toString());
        }
        s = new SoundingStation (wmoID, latvalue, lonvalue, elevvalue);
        stations.add(s);
    }
  

    /**  Creates a sounding observation with an empty raob */
    private void makeSoundingOb (int [] index, SoundingStation station, 
DateTime sndTime)  {
        soundings.add (new SoundingOb (station, sndTime));
        if (!times.contains(sndTime)) times.add(sndTime);
    }

    /**  Fills in the data for the RAOB */
    private void setRAOBData (int [] index, SoundingOb sound) {
        Variable press;
        Variable height;
        Variable temp;
        Variable dewpt;
        Variable direct;
        Variable speed;
        Unit pUnit = null;
        Unit tUnit = null;
        Unit tdUnit = null;
        Unit spdUnit = null;
        Unit dirUnit = null;
        Unit zUnit = null;



        int i;
        int numLevels;

        float p[];
        float t[];
        float td[];
        float z[];
        float spd[];
        float dir[];

        int[] j = new int [2];

        j[0] = index[0];
        int levFill;
        float pFill;
        float zFill;
        float tpFill;
        float tdFill;
        float spdFill;
        float dirFill;


        dbPrint ("\nNew Station:\n\t" + sound.getStation());

        // get the mandatory levels first
        if (hasMandP)  {
            try  {
                numLevels = numMandP.getInt(index);
                levFill = (int) getFillValue(numMandP, missingValue);
                if (numLevels > 0 && numLevels != levFill)  {
                    dbPrint ("Num mand pressure levels = " +   numLevels);
                    // Get the variables and their units
                    press  = nc.get(prMandPVar);
                    pUnit = getUnit(press);
                    pFill = getFillValue(press, missingValue);

                    height  = nc.get(htMandPVar);
                    // NB: geopotential altitudes stored in units of length
                    zUnit =
                        GeopotentialAltitude.getGeopotentialUnit(
                                                                 
getUnit(height));
                    zFill = getFillValue(height, missingValue);

                    temp  = nc.get(tpMandPVar);
                    tUnit = getUnit(temp);
                    tpFill = getFillValue(temp, missingValue);

                    dewpt  = nc.get(tdMandPVar);
                    tdUnit = getUnit(dewpt);
                    tdFill = getFillValue(dewpt, missingValue);

                    speed  = nc.get(spdMandPVar);
                    spdUnit = getUnit(speed);
                    spdFill = getFillValue(speed, missingValue);

                    direct  = nc.get(dirMandPVar);
                    dirUnit = getUnit(direct);
                    dirFill = getFillValue(direct, missingValue);
    
                    // initialize the arrays
                    p   = new float[numLevels];
                    z   = new float[numLevels];
                    t   = new float[numLevels];
                    td  = new float[numLevels];
                    spd = new float[numLevels];
                    dir = new float[numLevels];

                    // fill the arrays
                    for (i = 0; i < numLevels; i++) {
                        j[1] = i;
                        p[i] = press.getFloat(j) == pFill 
                            ? Float.NaN
                            : press.getFloat(j);
                        z[i]   = height.getFloat(j) == zFill
                            ? Float.NaN
                            : height.getFloat(j);
                        t[i]   = temp.getFloat(j) == tpFill
                            ? Float.NaN
                            : temp.getFloat(j);
                        td[i]  = 
                            dewpt.getFloat(j) == tdFill
                            ? Float.NaN
                            : dewpointIsDepression == true
                            ? t[i] - dewpt.getFloat(j)
                            : dewpt.getFloat(j);

                        spd[i] = speed.getFloat(j) == spdFill
                            ? Float.NaN
                            : speed.getFloat(j);
                        dir[i] = direct.getFloat(j) == dirFill
                            ? Float.NaN
                            : direct.getFloat(j);
                    }
                    sound.getRAOB().setMandatoryPressureProfile(pUnit, p, 
tUnit, t, tdUnit, td, spdUnit, spd, 
                                                                dirUnit, dir, 
zUnit, z);
                }
                else if (debug) {
                    System.out.println("No mandatory pressure data found for 
this station");
                }
            } catch (Exception e) {
                logException ("Unable to set mandatory pressure  data for 
station " + sound.getStation(), e);
            }
        }

        // now get the mandatory wind data
        if (hasMandW)  {
            try {
                numLevels = numMandW.getInt(index);
                levFill = (int) getFillValue(numMandW, missingValue);
                if (numLevels > 0 && numLevels != levFill)  {
                    if (debug) 
                        System.out.println("Num mand wind levels = "+ 
numLevels);

                    // Get the variables and their units
                    height  = nc.get(htMandWVar);
                    // NB: geopotential altitudes stored in units of length
                    zUnit = 
                        GeopotentialAltitude.getGeopotentialUnit(
                                                                 
getUnit(height));
                    zFill = getFillValue(height, missingValue);

                    speed  = nc.get(spdMandWVar);
                    spdUnit = getUnit(speed);
                    spdFill = getFillValue(speed, missingValue);

                    direct  = nc.get(dirMandWVar);
                    dirUnit = getUnit(direct);
                    dirFill = getFillValue(direct, missingValue);
    
                    // initialize the arrays
                    z   = new float[numLevels];
                    spd = new float[numLevels];
                    dir = new float[numLevels];

                    // fill the arrays
                    for (i = 0; i < numLevels; i++) {
                        j[1] = i;
                        z[i]   = height.getFloat(j) == zFill
                            ? Float.NaN
                            : height.getFloat(j);
                        spd[i] = speed.getFloat(j) == spdFill
                            ? Float.NaN
                            : speed.getFloat(j);
                        dir[i] = direct.getFloat(j) == dirFill
                            ? Float.NaN
                            : direct.getFloat(j);
                    }
                    sound.getRAOB().setMandatoryWindProfile(zUnit, z, spdUnit, 
spd, dirUnit, dir);
                }
                else if (debug)
                    System.out.println("No mandatory wind data found " +
                                       "for this station");
            } catch (Exception e)  {
                logException ("Unable to set mandatory wind data for station " 
+ sound.getStation(), e);
            }
        }

        // get the significant temperature levels
        if (hasSigT) {
            try {
                numLevels = numSigT.getInt(index);
                levFill = (int) getFillValue(numSigT, missingValue);
                if (numLevels > 0 && numLevels != levFill) {
                    if (debug)
                        System.out.println("Num sig temperature levels = " + 
                                           numLevels);
                    // Get the variables and their units
                    press  = nc.get(prSigTVar);
                    pUnit = getUnit(press);
                    pFill = getFillValue(press, missingValue);

                    temp  = nc.get(tpSigTVar);
                    tUnit = getUnit(temp);
                    tpFill = getFillValue(temp, missingValue);

                    dewpt  = nc.get(tdSigTVar);
                    tdUnit = getUnit(dewpt);
                    tdFill = getFillValue(dewpt, missingValue);

                    // initialize the arrays
                    p   = new float[numLevels];
                    t   = new float[numLevels];
                    td  = new float[numLevels];

                    // fill the arrays
                    for (i = 0; i < numLevels; i++)  {
                        j[1] = i;
                        p[i] = press.getFloat(j) == pFill 
                            ? Float.NaN
                            : press.getFloat(j);
                        t[i]   = temp.getFloat(j) == tpFill
                            ? Float.NaN
                            : temp.getFloat(j);
                        /*
                          td[i]  = dewpt.getFloat(j) == tdFill
                          ? Float.NaN
                          : dewpt.getFloat(j);
                        */
                        td[i]  = dewpt.getFloat(j) == tdFill
                            ? Float.NaN
                            : dewpointIsDepression == true
                            ? t[i] - dewpt.getFloat(j)
                            : dewpt.getFloat(j);
                    }
                    sound.getRAOB().setSignificantTemperatureProfile(pUnit, p, 
tUnit, t, tdUnit, td);
                }
                else if (debug)
                    System.out.println("No sig temperature data found " +
                                       "for this station");
            } catch (Exception e)  {
                logException ("Unable to set significant temperature data for 
station " + sound.getStation(), e);
            }
        }

        // get the significant levels with respect to wind 
        if (hasSigW)  {
            try {
                numLevels = numSigW.getInt(index);
                levFill = (int) getFillValue(numSigW, missingValue);
                if (numLevels > 0 && numLevels != levFill)  {
                    if (debug) 
                        System.out.println("Num significant wind levels = " + 
                                           numLevels);
                    // Get the variables and their units
                    height  = nc.get(htSigWVar);
                    // NB: geopotential altitudes stored in units of length
                    zUnit = 
                        GeopotentialAltitude.getGeopotentialUnit(
                                                                 
getUnit(height));
                    zFill = getFillValue(height, missingValue);

                    speed  = nc.get(spdSigWVar);
                    spdUnit = getUnit(speed);
                    spdFill = getFillValue(speed, missingValue);

                    direct  = nc.get(dirSigWVar);
                    dirUnit = getUnit(direct);
                    dirFill = getFillValue(direct, missingValue);
    
                    // initialize the arrays
                    z   = new float[numLevels];
                    spd = new float[numLevels];
                    dir = new float[numLevels];

                    // fill the arrays
                    for (i = 0; i < numLevels; i++) {
                        j[1] = i;
                        z[i]   = height.getFloat(j) == zFill
                            ? Float.NaN
                            : height.getFloat(j);
                        spd[i] = speed.getFloat(j) == spdFill
                            ? Float.NaN
                            : speed.getFloat(j);
                        dir[i] = direct.getFloat(j) == dirFill
                            ? Float.NaN
                            : direct.getFloat(j);
                    }
                    sound.getRAOB().setSignificantWindProfile (zUnit, z, 
spdUnit, spd, dirUnit, dir);
                } else {
                    dbPrint ("No significant wind data found for this station");
                }
            }  catch (Exception e) {
                logException  ("Unable to set significant wind data for station 
" + sound.getStation(), e);
            }
        }

        // now get the max wind level
        if (hasMaxW) {
            boolean multiLevel = false;
            try {
                numLevels = numMaxW.getInt(index);
                levFill = (int) getFillValue(numMaxW, missingValue);
                if (numLevels > 0 && numLevels != levFill) {
                    if (debug) 
                        System.out.println("Num max wind levels = " + 
                                           numLevels);
                    // Get the variables and their units
                    press  = nc.get(prMandPVar);
                    // check to see if it handles multipl levels
                    if (press.getRank() > 1) multiLevel = true;
                    pUnit = getUnit(press);
                    pFill = getFillValue(press, missingValue);

                    speed  = nc.get(spdMandPVar);
                    spdUnit = getUnit(speed);
                    spdFill = getFillValue(speed, missingValue);

                    direct  = nc.get(dirMandPVar);
                    dirUnit = getUnit(direct);
                    dirFill = getFillValue(direct, missingValue);
    
                    // initialize the arrays
                    p   = new float[numLevels];
                    spd = new float[numLevels];
                    dir = new float[numLevels];

                    // fill the arrays
                    if (!multiLevel) j = new int[] {index[0]};
                    for (i = 0; i < numLevels; i++) {
                        if (multiLevel) j[1] = i;
                        p[i] = press.getFloat(j) == pFill 
                            ? Float.NaN
                            : press.getFloat(j);
                        spd[i] = speed.getFloat(j) == spdFill
                            ? Float.NaN
                            : speed.getFloat(j);
                        dir[i] = direct.getFloat(j) == dirFill
                            ? Float.NaN
                            : direct.getFloat(j);
                    }
                    sound.getRAOB().setMaximumWindProfile(pUnit, p, spdUnit, 
spd, dirUnit, dir);
                }
                else if (debug)
                    System.out.println("No maximum wind data found " +
                                       "for this station");
            } catch (Exception e) {
                logException ("Unable to set maximum wind data for station " + 
sound.getStation(), e);
            }
        }

        // get the tropopause levels
        if (hasTrop) {
            try {
                numLevels = oneTrop ? 1 : numTrop.getInt(index);
                levFill = oneTrop 
                    ? (int) missingValue 
                    : (int) getFillValue(numTrop, missingValue);
                if (numLevels > 0 && numLevels != levFill)  {
                    if (debug) System.out.println("Num tropopause levels = " + 
                                                  numLevels);
                    // Get the variables and their units
                    press  = nc.get(prTropVar);
                    pUnit = getUnit(press);
                    pFill = getFillValue(press, missingValue);

                    temp  = nc.get(tpTropVar);
                    tUnit = getUnit(temp);
                    tpFill = getFillValue(temp, missingValue);

                    dewpt  = nc.get(tdTropVar);
                    tdUnit = getUnit(dewpt);
                    tdFill = getFillValue(dewpt, missingValue);

                    speed  = nc.get(spdTropVar);
                    spdUnit = getUnit(speed);
                    spdFill = getFillValue(speed, missingValue);

                    direct  = nc.get(dirTropVar);
                    dirUnit = getUnit(direct);
                    dirFill = getFillValue(direct, missingValue);
    
                    // initialize the arrays
                    p   = new float[numLevels];
                    t   = new float[numLevels];
                    td  = new float[numLevels];
                    spd = new float[numLevels];
                    dir = new float[numLevels];
   
                    // fill the arrays
                    if (oneTrop) j = new int[] {index[0]};
                    if (!oneTrop || (oneTrop && press.getFloat(j) != pFill))  {
                        for (i = 0; i < numLevels; i++) {
                            if (!oneTrop) j[1] = i;
                            p[i] = press.getFloat(j) == pFill 
                                ? Float.NaN
                                : press.getFloat(j);
                            t[i]   = temp.getFloat(j) == tpFill
                                ? Float.NaN
                                : temp.getFloat(j);
                            /*
                              td[i]  = dewpt.getFloat(j) == tdFill
                              ? Float.NaN
                              : dewpt.getFloat(j);
                            */
                            td[i]  = dewpt.getFloat(j) == tdFill
                                ? Float.NaN
                                : dewpointIsDepression == true
                                ? t[i] - dewpt.getFloat(j)
                                : dewpt.getFloat(j);
                            spd[i] = speed.getFloat(j) == spdFill
                                ? Float.NaN
                                : speed.getFloat(j);
                            dir[i] = direct.getFloat(j) == dirFill
                                ? Float.NaN
                                : direct.getFloat(j);
                        }
                        sound.getRAOB().setTropopauseProfile(
                                                             
RAOB.newTropopauseProfile(
                                                                                
       pUnit, p, tUnit, t, tdUnit, td, spdUnit, spd, 
                                                                                
       dirUnit, dir));
                    }
                }
                else if (debug)
                    System.out.println("No tropopause data found " +
                                       "for this station");
            } catch (Exception e) {
                logException ("Unable to set tropopause data for station " + 
sound.getStation(), e);

            }
        }

    }

    /**  create a DateTime object for the soundingob */
    private DateTime getObsTime(int[] index) {
        // if first time through, get some metadata
        try {
            if (timeUnit == null) {
                timeUnit = getUnit(time);
                if (timeUnit == null) timeUnit = RealType.Time.getDefaultUnit();
                Attribute a = time.getAttribute("_FillValue");
                if (a != null) timeFill = a.getNumericValue().doubleValue();
            }
        } catch (Exception ve) {
            timeUnit = RealType.Time.getDefaultUnit();
            timeFill = Double.NaN;
        }
        try  {
           double val = time.getDouble(index);

           return  
               (Double.doubleToLongBits(val) == 
                Double.doubleToLongBits(timeFill))
                  ? (DateTime)null
                  : (timeUnit == null)
                      ? new DateTime(val)
                      : new DateTime( new Real(RealType.Time, val, timeUnit));
        } catch (Exception ne)  {
            logException ("getObsTime", ne);
        }
        return null;
    }

    protected String getDflt (String name, String dflt) {
        return getDflt ("NetcdfSoundingAdapter", name, dflt);
    }

    /**  
     * Determines the names of the variables in the netCDF file that
     * should be used.
     */
    private void getVariables()
        throws Exception {
        stid =  nc.get (getDflt ("stationIDVariable", "wmoStaNum"));
        if (stid == null)
            throw new Exception("Unable to find station id variable");

        lat = nc.get(getDflt("latitudeVariable", "staLat"));
        if (lat == null)
            throw new Exception("Unable to find latitude variable");

        lon = nc.get(getDflt("longitudeVariable", "staLon"));
        if (lon == null)
            throw new Exception("Unable to find longitude variable");

        elev = nc.get(getDflt("stationElevVariable", "staElev"));
        if (elev == null)
            throw new Exception("Unable to find station elevation variable");

        Attribute a = nc.getAttribute("timeVariables");
        time =  nc.get(
           (a != null)
              ? a.getStringValue()
              : getDflt("soundingTimeVariable", "relTime"));
        if (time == null)
            throw new Exception("Unable to find sounding time variable");

        numMandP =  nc.get(getDflt("numMandPresLevels", "numMand"));

        if (numMandP != null) {
            hasMandP = true;
            prMandPVar = getDflt("mandPPressureVariable", "prMan");
            htMandPVar = getDflt("mandPHeightVariable", "htMan");
            tpMandPVar = getDflt("mandPTempVariable", "tpMan");
            tdMandPVar = getDflt("mandPDewptVariable", "tdMan");
            spdMandPVar = getDflt("mandPWindSpeedVariable", "wsMan");
            dirMandPVar = getDflt("mandPWindDirVariable", "wdMan");
        }
            
        numMandW =  nc.get(getDflt("numMandWindLevels", "numMandW"));
        if (numMandW != null) {
            hasMandW = true;
            htMandWVar = getDflt("mandWHeightVariable", "htMandW");
            spdMandWVar = getDflt("mandWWindSpeedVariable", "wsMandW");
            dirMandWVar = getDflt("mandWWindDirVariable", "wdMandW");
        }
            
        numSigT      = nc.get(getDflt("numSigTempLevels", "numSigT"));
        if (numSigT != null) {
            hasSigT = true;
            prSigTVar = getDflt("sigTPressureVariable", "prSigT");
            tpSigTVar = getDflt("sigTTempVariable", "tpSigT");
            tdSigTVar = getDflt("sigTDewptVariable", "tdSigT");
        }
            
        numSigW =  nc.get(getDflt("numSigWindLevels", "numSigW"));
        if (numSigW != null) {
            hasSigW = true;
            htSigWVar = getDflt("sigWHeightVariable", "htSigW");
            spdSigWVar = getDflt("sigWWindSpeedVariable", "wsSigW");
            dirSigWVar = getDflt("sigWWindDirVariable", "wdSigW");
        }
            
        numMaxW =  nc.get(getDflt("numMaxWindLevels", "numMwnd"));
        if (numMaxW != null) {
            hasMaxW = true;
            prMaxWVar = getDflt("maxWPressureVariable", "prMaxW");
            spdMaxWVar = getDflt("maxWWindSpeedVariable", "wsMaxW");
            dirMaxWVar = getDflt("maxWWindDirVariable", "wdMaxW");
        }
            
        numTrop = nc.get(getDflt("numTropLevels", "numTrop"));
        if (numTrop == null) {
            // see if this is the old version (one trop level)
            if (nc.contains(getDflt("prTropName", "prTrop"))) {
                hasTrop = true;
                oneTrop = true;
            }
        }
        else {
            hasTrop = true;
        }

        if (hasTrop) {
            prTropVar = getDflt ("tropPressureVariable", "prTrop");
            tpTropVar = getDflt ("tropTempVariable", "tpTrop");
            tdTropVar = getDflt ("tropDewptVariable", "tdTrop");
            spdTropVar = getDflt ("tropWindSpeedVariable", "wsTrop");
            dirTropVar = getDflt ("tropWindDirVariable", "wdTrop");
        }

        // Check to see if dewpoint is stored as a depression or actual value.
        dewpointIsDepression =  Boolean.valueOf(getDflt("dewpointIsDepression", 
"true")).booleanValue();
        // See if there is a default value for missing data
        try {
            missingValue = Float.parseFloat(getDflt("missingValue", "99999"));
        }   catch (NumberFormatException excp)  {
            missingValue = 99999;
        }
    }

    /** gets the units of the variable */
    private Unit getUnit(Variable v) {
        Unit u = null;
        Attribute a = v.getAttribute("units");
        if (a != null) {
            try  {
                u = Parser.parse(a.getStringValue());
            } catch (Exception e) {
                u = null;
            }
        }
        return  u;
    }

    /** gets the fill value for the variable or if none, returns
        a default value */
    private float getFillValue(Variable v, float defaultValue) {
        Attribute a = v.getAttribute("_FillValue");
        return (a == null) ? defaultValue
            : a.getNumericValue().floatValue();

    }
}