/*
* Copyright 1998, University Corporation for Atmospheric Research
* All Rights Reserved.
* See file LICENSE for copying and redistribution conditions.
*
* $Id: View.java,v 1.9 2006-02-13 22:30:08 curtis Exp $
*/
package visad.data.netcdf.in;
import java.io.IOException;
import java.util.Map;
import java.util.WeakHashMap;
import ucar.netcdf.Attribute;
import ucar.netcdf.Dimension;
import ucar.netcdf.DimensionIterator;
import ucar.netcdf.Netcdf;
import ucar.netcdf.Variable;
import ucar.netcdf.VariableIterator;
import visad.CoordinateSystem;
import visad.data.in.ArithProg;
import visad.data.netcdf.Quantity;
import visad.data.netcdf.QuantityDB;
import visad.data.units.Parser;
import visad.DoubleSet;
import visad.ErrorEstimate;
import visad.FloatSet;
import visad.Gridded1DDoubleSet;
import visad.Gridded1DSet;
import visad.Integer1DSet;
import visad.Linear1DSet;
import visad.RealType;
import visad.ScalarType;
import visad.SI;
import visad.SimpleSet;
import visad.TextType;
import visad.TypeException;
import visad.Unit;
import visad.VisADException;
/**
* A convention-dependent view of a netCDF dataset.
*
* @author Steven R. Emmerson
* @version $Revision: 1.9 $ $Date: 2006-02-13 22:30:08 $
*/
public abstract class View
{
/**
* The netCDF dataset that is being viewed through these conventions.
*/
private final Netcdf netcdf;
/**
* The quantity database to use to map netCDF variables to VisAD
* Quantity-s.
*/
private final QuantityDB quantityDB;
/**
* Flag for whether this View handles char variables as Text
*/
private final boolean charToText;
/*
* Performance caches:
*/
private final Map varToRealType;
private final Map varToTextType;
private final Map varToUnit;
private final Map dimToSet;
private final Map dimToRealType;
/*
* Well-known quantities for comparison purposes:
*/
private final Quantity longitude;
private final Quantity latitude;
/**
* Something for creating unique names.
*/
private static int nameCount;
/** Names of outer dimensions to be factored out
*
*/
private java.util.Set factorNameSet = null;
/**
* Constructs from a netCDF dataset.
*
* @param netcdf The netCDF dataset.
* @param quantityDB The quantity database to use to map netCDF
* variables to VisAD Quantity-s.
* @throws NullPointerException if either argument is <code>null</code>.
*/
protected View(Netcdf netcdf, QuantityDB quantityDB)
{
this(netcdf, quantityDB, false);
}
/**
* Constructs from a netCDF dataset.
*
* @param netcdf The netCDF dataset.
* @param quantityDB The quantity database to use to map netCDF
* variables to VisAD Quantity-s.
* @param charToText Specifies whether the View should map char
* variables to VisAD Text objects
* @throws NullPointerException if either argument is <code>null</code>.
*/
protected View(Netcdf netcdf, QuantityDB quantityDB, boolean charToText)
{
this.netcdf = netcdf;
this.quantityDB = quantityDB;
this.charToText = charToText;
varToUnit = new WeakHashMap();
dimToSet = new WeakHashMap();
varToRealType = new WeakHashMap();
varToTextType = new WeakHashMap();
dimToRealType = new WeakHashMap();
longitude = quantityDB.get("longitude");
latitude = quantityDB.get("latitude");
}
/**
* Returns a view of a netCDF dataset. The exact view returned depends
* on the netCDF dataset.
*
* @param netcdf The netCDF dataset.
* @param db A quantity database to be used to map netCDF
* variables to VisAD {@link Quantity}s.
* @return A view of the dataset.
*/
public static View getInstance(Netcdf netcdf, QuantityDB db)
{
return getInstance(netcdf, db, false);
}
/**
* Returns a view of a netCDF dataset. The exact view returned depends
* on the netCDF dataset.
*
* @param netcdf The netCDF dataset.
* @param db A quantity database to be used to map netCDF
* variables to VisAD {@link Quantity}s.
* @param charToText Specifies whether the View should map char
* variables to VisAD Text objects
* @return A view of the dataset.
*/
public static View getInstance(
Netcdf netcdf, QuantityDB db, boolean charToText)
{
View view;
String conventions = getConventionsString(netcdf);
if (conventions == null)
{
view = new DefaultView(netcdf, db, charToText);
}
else
{
try
{
if (conventions.equals("CF-1.0"))
view = new CfView(netcdf, db, charToText);
else if (conventions.equals("COARDS"))
view = new CfView(netcdf, db);
else if (conventions.equals("COARDS/CF-1.0"))
view = new CfView(netcdf, db, charToText);
else
{
System.err.println(
"Unknown netCDF conventions attribute (" +
conventions + "). Using default view...");
view = new DefaultView(netcdf, db, charToText);
}
}
catch (IllegalArgumentException e)
{
System.err.println(
"netCDF dataset doesn't follow stated conventions (" +
conventions + "): " + e.getMessage() +
"\nUsing default view...");
view = new DefaultView(netcdf, db, charToText);
}
}
return view;
}
/**
* Does this View handle text.
*
* @return true if text is handled
*/
public boolean isCharToText() {
return charToText;
}
/**
* Returns the underlying netCDF dataset.
*
* @return The netCDF dataset.
*/
public Netcdf getNetcdf()
{
return netcdf;
}
/**
* <p>Returns the value of the global "Conventions" attribute. If the
* attribute doesn't exist or is invalid, then <code>null</code> is
* returned. If the attribute exists but is not string-valued, then an
* error message is printed to {@link System#err} and <code>null</code> is
* returned.</p>
*
* @param netcdf The netCDF dataset.
* @return The value of the attribute.
*/
protected static String getConventionsString(Netcdf netcdf)
{
Attribute attr = netcdf.getAttribute("Conventions");
try
{
return attr != null ? attr.getStringValue() : null;
}
catch (ClassCastException e)
{
System.err.println("The \"Conventions\" attribute (" + attr +
") isn't a string");
return null;
}
}
/**
* Returns the named netCDF variable. Returns <code>null</code> if the
* variable doesn't exist.
*
* @param name The name of the netCDF variable.
* @throws NullPointerException if the name is <code>null</code>.
* @return The named netCDF variable or
* <code>null</code>.
*/
protected Variable getVariable(String name)
{
return netcdf.get(name);
}
/**
* Indicates if the netCDF variable with a given name is numeric.
*
* @param name The name of the netCDF variable.
* @return <code>true</code> if and only if the
* variable exists has numeric values.
*/
protected boolean isNumeric(String name)
{
Variable var = netcdf.get(name);
if (var == null)
return false;
return isNumeric(var);
}
/**
* Indicates if the given netCDF variable is numeric.
*
* @param var The netCDF variable.
* @return <code>true</code> if and only if the
* variable is numeric.
* @throws NullPointerException if the argument is <code>null</code>.
*/
protected boolean isNumeric(Variable var)
{
return !var.getComponentType().equals(char.class);
}
/**
* Indicates if a netCDF dimension represents longitude. This method uses
* {@link #getRealType(Variable)} and {@link #isLongitude(RealType)}.
*
* @param var A netCDF dimension.
* @return <code>true</code> if an only if <code>dim</code>
* represents longitude.
* @throws VisADException Couldn't create necessary VisAD object.
*/
protected boolean isLongitude(Variable var)
throws VisADException
{
RealType type = getRealType(var);
return type != null && type.equals(longitude);
}
/**
* Indicates if a VisAD {@link RealType} represents longitude.
*
* @param type A VisAD {@link RealType}. May be
* <code>null</code>.
* @return <code>true</code> if an only if the VisAD
* {@link RealType} represents longitude.
*/
protected boolean isLongitude(RealType type)
{
return type != null && type.equals(longitude);
}
/**
* Indicates if a VisAD {@link RealType} represents latitude.
*
* @param type A VisAD {@link RealType}. May be
* <code>null</code>.
* @return <code>true</code> if an only if the VisAD
* {@link RealType} represents latitude.
*/
protected boolean isLatitude(RealType type)
{
return type != null && type.equals(latitude);
}
/**
* <p>Returns the VisAD {@link visad.MathType} of the domain corresponding to a
* netCDF dimension.</p>
*
* <p>This implementation supports coordinate variables and uses {@link
* #getCoordinateVariable(Dimension)} and {@link #getRealType(Variable)}.
* </p>
*
* @param dim A netCDF dimension.
* @return The VisAD MathType of the domain corresponding
* to <code>dim</code>. Won't be <code>null</code>.
* @throws TypeException if a corresponding {@link visad.RealType} needed
* to be created but couldn't.
*/
protected RealType getRealType(Dimension dim)
throws TypeException
{
RealType type;
Variable var = getCoordinateVariable(dim);
if (var != null)
{
type = getRealType(var);
}
else
{
synchronized(dimToRealType)
{
type = (RealType)dimToRealType.get(dim);
if (type == null)
{
String name = dim.getName();
type = quantityDB.get(name);
if (type == null) {
type = RealType.getRealType(name);
}
if (type == null) {
throw new TypeException(
"Couldn't create RealType for " + dim.getName());
}
dimToRealType.put(dim, type);
}
}
}
return type;
}
/**
* <p>Returns the VisAD {@link RealType} of a netCDF variable.</p>
*
* <p>This implementation returns the value of {@link
* #getRealTypeFromLongName(Variable)} if that is non-<code>null</code>;
* otherwise, the value of {@link #getRealTypeFromName(Variable)} is
* returned.</p>
*
* @param var The netCDF variable.
* @return The corresponding VisAD RealType.
* @throws NullPointerException if the argument is <code>null</code>.
* @throws TypeException if a corresponding {@link RealType} needed
* to be created but couldn't.
*/
protected RealType getRealType(Variable var)
throws TypeException
{
if (var == null)
throw new NullPointerException();
RealType type;
/*
* To improve performance, this method caches results.
*/
synchronized(varToRealType)
{
type = (RealType)varToRealType.get(var);
if (type == null)
{
type = getRealTypeFromLongName(var);
if (type == null)
type = getRealTypeFromName(var);
varToRealType.put(var, type); // cache result
}
}
return type;
}
/**
* Returns the VisAD RealType corresponding to the <code>long_name</code>
* attribute of a netCDF variable. If the unit attribute of the variable
* is incompatible with the unit of the <code>long_name</code> attribute,
* then a new RealType is created whose default unit is that of the
* attribute.</p>
*
* <p>This implementation first checks if the variable has a
* <code>long_name</code> attribute via {@link #getLongName(Variable)},
* if it doesn't, then <code>null</code> is returned; otherwise, the
* long name is used to query the quantity database. If the quantity
* database doesn't contain a match, then <code>null</code> is returned;
* otherwise, the variable's unit attribute -- obtained via {@link
* #getUnitFromAttribute(Variable)} -- is checked. If the unit attribute
* doesn't exist, then the {@link RealType} is returned; otherwise, the unit
* attribute is compared against the default unit of the {@link RealType}.
* If the two are convertible, then the {@link RealType} is returned;
* otherwise, an attempt is made to create a new {@link RealType} with a
* slightly different name than the variable's but with the same unit as the
* variable's and that {@link RealType} is returned.</p>
*
* @param var The netCDF variable.
* @return The corresponding VisAD RealType or
* <code>null</code> if no corresponding type
* was found or could be created.
*/
protected RealType getRealTypeFromLongName(Variable var)
{
RealType type;
String name = getLongName(var);
if (name == null)
{
type = null;
}
else
{
type = quantityDB.get(name);
if (type != null)
{
Unit unit = getUnitFromAttribute(var);
if (!Unit.canConvert(unit, type.getDefaultUnit()))
{
String newName = newName(var);
System.err.println(
"The unit attribute (" + unit + ") " +
"of variable \"" + var.getName() + "\" " +
"is incompatible with the unit (" +
type.getDefaultUnit() + ") " +
"of the quantity referenced by the long_name attribute "
+ "(" + name + "). " +
"Attempting to create new quantity \"" + newName +
"\".");
type = RealType.getRealType(newName, unit);
}
}
}
return type;
}
/**
* <p>Returns the VisAD {@link RealType} corresponding to the name of a
* netCDF variable. <code>null</code> is never returned.</p>
*
* <p>This implementation first obtains the variable's unit via {@link
* #getUnitFromAttribute(Variable)}. It then queries the quantity database
* for a match to the variable's name. If a match is found, then variable's
* unit is checked. If the unit is <code>null</code>, then the {@link
* RealType} from the database is returned; otherwise, the unit is checked
* against the default unit of the obtained {@link RealType}. If the two
* units are convertible, then the {@link RealType} is returned; otherwise,
* a new {@link RealType} is created that has a slightly different name than
* the variable's but with the variable's unit and that {@link RealType}
* is returned. If the quantity database doesn't contain a match, then
* the variable's unit is checked. If it's <code>null</code>, then
* the value of {@link RealType#getRealType(String)} -- when given the
* variable's name -- is returned; otherwise, the return value of {@link
* RealType#getRealType(String, Unit)} -- when invoked with the variable's
* name and unit -- is checked. If it's non-<code>null</code>, then that
* {@link RealType} is returned; otherwise, a new {@link RealType} is
* created that has a slightly different name than the variable's but with
* the variable's unit and that {@link RealType} is returned.</p>
*
* @param var The netCDF variable.
* @return The corresponding VisAD RealType.
* @throws TypeException if a corresponding {@link RealType} needed
* to be created but couldn't.
*/
protected RealType getRealTypeFromName(Variable var)
throws TypeException
{
String name = var.getName();
Unit unit = getUnitFromAttribute(var);
RealType type = quantityDB.get(name);
if (type != null)
{
if (!Unit.canConvert(unit, type.getDefaultUnit()))
type = newQuantity(var, unit, type.getDefaultUnit());
}
else
{
if (unit == null)
{
type = RealType.getRealType(name);
}
else
{
type = RealType.getRealType(name, unit);
if (type == null)
{
type =
newQuantity(
var,
unit,
RealType.getRealTypeByName(name).getDefaultUnit());
}
}
}
return type;
}
private RealType newQuantity(Variable var, Unit wantUnit, Unit haveUnit)
throws TypeException
{
String newName = newName(var);
System.err.println(
"The unit attribute (" + wantUnit + ") " +
"of variable \"" + var.getName() + "\" " +
"is incompatible with the unit (" +
haveUnit + ") of the RealType of the same name. " +
"Attempting to create new RealType \"" + newName + "\".");
RealType type = RealType.getRealType(newName, wantUnit);
if (type == null)
throw new TypeException(newName);
return type;
}
/**
* Returns a name for a given variable that is slightly different that the
* variable's name and is guarenteed not to have been returned before.
*
* @param var The netCDF variable.
* @return A new and unique name based on the
* variable.
*/
protected String newName(Variable var)
{
return var.getName() + "_" + nameCount++;
// getUnitFromAttribute(var).toString().replace(' ', '_')
// .replace('.', '_');
}
/**
* <p>Gets the type of the values of a netCDF variable.</p>
*
* <p>This implementation returns the value of #getRealType(Variable)}
* or {@link #getTextType(Variable)} -- depending on the value of {@link
* #isNumeric(Variable)}.</p>
*
* @param var A netCDF variable.
* @throws TypeException if a corresponding {@link RealType} needed
* to be created but couldn't.
* @throws VisADException if a VisAD object can't be created.
*/
protected ScalarType getScalarType(Variable var)
throws TypeException, VisADException
{
return
isNumeric(var)
? (ScalarType)getRealType(var)
: (ScalarType)getTextType(var);
}
/**
* Return the VisAD TextType of a netCDF variable.
*
* @param var The netCDF variable.
* @return The VisAD TextType of <code>var</code>.
* @throws VisADException if a VisAD object couldn't be created.
* @throws IllegalArgumentException
* if the netCDF variable is not textual.
*/
protected TextType getTextType(Variable var)
throws VisADException
{
if (var == null)
throw new NullPointerException();
if (isNumeric(var))
throw new IllegalArgumentException(var.toString());
TextType type;
/*
* To improve performance, this method caches results.
*/
synchronized(varToTextType)
{
type = (TextType)varToTextType.get(var);
if (type == null)
{
type = TextType.getTextType(var.getName());
varToTextType.put(var, type); // cache result
}
}
return type;
}
/**
* Gets the representational set for the values of a netCDF variable. If
* the variable isn't numeric, then <code>null</code> is returned.
*
* <p>This implementation uses {@link #getRealType(Variable)}, {@link
* #getVetter(Variable)}, and {@link #getUnitFromAttribute(Variable)}.</p>
*
* @param var A netCDF variable.
* @return The VisAD representational set for the values of
* the variable or <code>null</code>.
* @throws TypeException if a corresponding {@link RealType} needed
* to be created but couldn't.
* @throws VisADException Couldn't create necessary VisAD object.
*/
protected SimpleSet getRangeSet(Variable var)
throws TypeException, VisADException
{
SimpleSet set;
Class cl = var.getComponentType();
if (cl.equals(char.class))
{
set = null;
}
else
{
RealType type = getRealType(var);
if (cl.equals(byte.class))
{
set = new Linear1DSet(type,
Byte.MIN_VALUE+1, Byte.MAX_VALUE,
Byte.MAX_VALUE - Byte.MIN_VALUE);
}
else if (cl.equals(short.class))
{
set = new Linear1DSet(type,
Short.MIN_VALUE+1, Short.MAX_VALUE,
Short.MAX_VALUE - Short.MIN_VALUE);
}
else if (cl.equals(int.class))
{
/*
* The following is complicated due to the fact that the last
* argument to the Linear1DSet() constructor:
*
* Linear1DSet(MathType type, double start, double stop,
* int length)
*
* is an "int" -- and the number of Java "int" values cannot
* be represented by a Java "int".
*/
Vetter vetter = getVetter(var);
long minValid = (long)vetter.minValid();
long maxValid = (long)vetter.maxValid();
long length = maxValid - minValid + 1;
set = length <= Integer.MAX_VALUE
? (SimpleSet)(new Linear1DSet(type, minValid,
maxValid, (int)length))
: (SimpleSet)(new FloatSet(type,
(CoordinateSystem)null,
new Unit[] {
getUnitFromAttribute(var)}));
}
else if (cl.equals(float.class))
{
set = new FloatSet(type, (CoordinateSystem)null,
new Unit[] {getUnitFromAttribute(var)});
}
else
{
set = (SimpleSet)new DoubleSet(type, (CoordinateSystem)null,
new Unit[] {getUnitFromAttribute(var)});
}
}
return set;
}
/**
* <p>Returns a string-valued global attribute. If the attribute doesn't
* exist or is invalid, then <code>null</code> is returned. If the
* attribute exists but is not string-valued, then an error message is
* printed to {@link System#err} and <code>null</code> is returned.</p>
*
* <p>This implementation uses {@link #getAttributeString(Variable, String)}.
* </p>
*
* @param name The name of the attribute.
* @return The string value of the attribute or
* <code>null</code>.
*/
protected String getAttributeString(String name)
{
return getAttributeString((Variable)null, name);
}
/**
* Returns a string-valued global attribute or a netCDF variable attribute.
* If the attribute doesn't exist or is invalid, then <code>null</code> is
* returned. If the attribute exists but is not string-valued, then an
* error message is printed to {@link System#err} and <code>null</code> is
* returned.
*
* @param var A netCDF variable or <code>null</code> to
* indicate a global attribute.
* @param name The name of the attribute.
* @return The string value of the attribute or
* <code>null</code>.
*/
protected String getAttributeString(Variable var, String name)
{
Attribute attr =
var == null ? netcdf.getAttribute(name) : var.getAttribute(name);
try
{
return attr != null ? attr.getStringValue() : null;
}
catch (ClassCastException e)
{
System.err.println(
"Non-string attribute: " + var.getName() + ":" + name);
return null;
}
}
/**
* <p>Returns the long name of a netCDF variable according to the variable's
* <code>long_name</code> attribute. If the attribute doesn't exist, then
* <code>null</code> is returned.</p>
*
* <p>This method uses {@link #getAttributeString(Variable, String)}.</p>
*
* @param var A netCDF variable.
* @return The long name of <code>var</code> or
* <code>null</code>.
* @throws ClassCastException
* if the attribute exists but its value isn't a
* String.
*/
protected String getLongName(Variable var)
{
return getAttributeString(var, "long_name");
}
/**
* <p>Returns the string value of the unit attribute of a netCDF variable.
* Returns <code>null</code> if the unit attribute is missing or
* invalid.</p>
*
* <p>This method uses {@link #getAttributeString(Variable, String)} --
* first with the name "units" and then with the name "unit".</p>
*
* @param var A netCDF variable.
* @return The unit of the values of <code>var</code> or
* <code>null</code>.
*/
protected String getUnitString(Variable var)
{
String str = getAttributeString(var, "units");
if (str == null)
str = getAttributeString(var, "unit");
return str;
}
/**
* <p>Returns the unit of a netCDF variable according to the variable's unit
* attribute. Returns <code>null</code> if the unit attribute is missing
* or invalid. If a unit specification exists but can't be decoded, then
* a warning message is printed to {@link System#err}.</p>
*
* <p>This method uses {@link #getUnitString(Variable)}.</p>
*
* @param var A netCDF variable.
* @return The unit of the values of <code>var</code> or
* <code>null</code>.
*/
protected Unit getUnitFromAttribute(Variable var)
{
Unit unit;
/*
* This method caches results to improve performance.
*/
synchronized(varToUnit)
{
/*
* The following two lines exist because the unit map is a
* WeakHashMap and may contain null values.
*/
unit = (Unit)varToUnit.get(var);
if (!varToUnit.containsKey(var))
{
String spec = getUnitString(var);
if (spec != null)
{
try
{
unit = Parser.parse(spec);
}
catch (Exception e)
{
System.err.println(
"Couldn't decode unit attribute (" + spec + ")" +
" of variable \"" + var.getName() + "\": " +
e.getMessage());
}
}
varToUnit.put(var, unit); // cache result
}
}
return unit;
}
/**
* Returns a value-vetter for a netCDF variable.
*
* @param var A netCDF variable.
* @return A value-vetter for the variable.
*/
protected Vetter getVetter(Variable var)
{
return new Vetter(var);
}
/**
* <p>Returns the VisAD {@link Gridded1DSet} corresponding to a netCDF
* dimension.</p>
*
* <p>This implementation supports coordinate variables, longitude,
* and the discovery of an arithmetic progression. It uses {@link
* #isLongitude(Variable)}, {@link #getRealType(Dimension)}, and {@link
* #getUnitFromAttribute(Variable)}. </p>
*
* @param dim A netCDF dimension.
* @return The VisAD {@link visad.GriddedSet} corresponding to
* the dimension.
* @throws VisADException if a VisAD object couldn't be created.
* @throws IOException if a netCDF read-error occurs.
* @throws ClassCastException
* if the dimension has a coordinate variable of
* improper type.
*/
protected Gridded1DSet getDomainSet(Dimension dim)
throws VisADException, IOException
{
/*
* This implementation caches earlier results because this operation is
* potentially expensive and may be invoked many times for any given
* dimension.
*/
Gridded1DSet set = (Gridded1DSet)dimToSet.get(dim);
if (set == null)
{
Variable coordVar = getCoordinateVariable(dim);
if (coordVar == null)
{
// TODO: add CoordinateSystem argument
set = new Integer1DSet(getRealType(dim), dim.getLength());
}
else
{
ArithProg ap = isLongitude(coordVar)
? new visad.data.in.LonArithProg()
: new visad.data.in.ArithProg();
Class varType = coordVar.getComponentType();
boolean isDouble = varType.equals(double.class);
Object coordValues;
if (isDouble)
{
coordValues = coordVar.toArray();
ap.accumulate((double[])coordValues);
}
else if (varType.equals(float.class))
{
coordValues = coordVar.toArray();
ap.accumulate((float[])coordValues);
}
else
{
int length = 1;
{
int[] lengths = coordVar.getLengths();
for (int i = 0; i < lengths.length; i++)
length *= lengths[i];
}
float[] floatVals = new float[length];
if (varType.equals(int.class))
{
int[] values = (int[])coordVar.toArray();
for (int i = 0; i < values.length; i++)
floatVals[i] = values[i];
}
else if (varType.equals(short.class))
{
short[] values = (short[])coordVar.toArray();
for (int i = 0; i < values.length; i++)
floatVals[i] = values[i];
}
else
{
byte[] values = (byte[])coordVar.toArray();
for (int i = 0; i < values.length; i++)
floatVals[i] = values[i];
}
ap.accumulate(floatVals);
coordValues = floatVals;
}
if (ap.isConsistent())
{
/*
* The coordinate-variable is an arithmetic progression.
*/
// TODO: add CoordinateSystem argument
set = new Linear1DSet(
getRealType(dim),
ap.getFirst(),
ap.getLast(),
(int)ap.getNumber(),
(CoordinateSystem)null,
new Unit[] {getUnitFromAttribute(coordVar)},
(ErrorEstimate[])null);
}
else
{
/*
* The coordinate-variable is not an arithmetic progression.
*/
// TODO: add CoordinateSystem argument
set =
isDouble
? (Gridded1DSet)new Gridded1DDoubleSet(
getRealType(dim),
new double[][] {(double[])coordValues},
dim.getLength(),
(CoordinateSystem)null,
new Unit[] {getUnitFromAttribute(coordVar)},
(ErrorEstimate[])null)
: new Gridded1DSet(
getRealType(dim),
new float[][] {(float[])coordValues},
dim.getLength(),
(CoordinateSystem)null,
new Unit[] {getUnitFromAttribute(coordVar)},
(ErrorEstimate[])null);
}
}
dimToSet.put(dim, set);
}
return set;
}
/**
* <p>Returns the netCDF coordinate variable associated with a netCDF
* dimension. If no such variable exists, then <code>null</code> is
* returned.</p>
*
* <p>This implementation uses {@link #isNumeric(Variable)}.</p>
*
* @param dim A netCDF dimension.
* @return The netCDF coordinate variable associated
* with the dimension or <code>null</code>
* if there is no coordinate variable.
*/
protected Variable getCoordinateVariable(Dimension dim)
{
Variable var = netcdf.get(dim.getName());
if (var != null && !(var.getRank() == 1 && isNumeric(var)))
var = null;
return var;
}
/** <p> Defines the names of domain components to factor out.
* This only works if this names correspond to the outermost
* dimension. The list of names may be changed after calling
* this.</p>
*
* <p>The Set should contain only String(s).</p>
*
* <p>Typically, a TreeSet will be used. For example:</p>
* <code>TreeSet ts = new TreeSet();</code>
* <code>ts.add("myParameter");</code>
* <code>view.setOuterDimensionNameSet(ts);</code>
*
* @param nameSet A Set containing the names (as Strings) of
* the dimensions to factor out.
*
*/
public void setOuterDimensionNameSet(java.util.Set nameSet) {
factorNameSet = nameSet;
}
/**
* <p> Returns the factorName object
*
* @return The Set of factorNames.
*/
public java.util.Set getOuterDimensionNameSet() {
return factorNameSet;
}
/**
* <p>Indicates if a netCDF dimension represents time.</p>
*
* <p>This implementation supports coordinate variables and uses {@link
* #getRealType(Dimension)}.</p>
*
* <p>If setOuterDimensionNameSet() has been called, this list
* of names will also logically be considered factorable.</p>
*
* @param dim A netCDF dimension.
* @return <code>true</code> if and only if the dimension
* represents time.
* @throws VisADException Couldn't create necessary VisAD object.
* @throws IOException I/O failure.
*/
protected boolean isTime(Dimension dim)
throws VisADException, IOException
{
RealType rt = getRealType(dim);
if (factorNameSet != null) {
if (factorNameSet.contains(rt.getName()) ) {
return true;
}
}
return (isTime(rt.getDefaultUnit()) );
}
/**
* Indicates if a unit is a unit of time.
*
* @param unit A unit.
* @return <code>true</code> if and only if the unit
* is a unit of time.
*/
protected boolean isTime(Unit unit)
{
return unit != null && SI.second.isConvertible(unit.getAbsoluteUnit());
}
/**
* Returns the netCDF dimensions of a netCDF variable.
*
* @param var A netCDF variable.
* @return The dimensions of <code>var</code> in
* netCDF order.
*/
protected Dimension[] getDimensions(Variable var)
{
int rank = var.getRank();
Dimension[] dims = new Dimension[rank];
DimensionIterator iter = var.getDimensionIterator();
for (int i = 0; i < rank; ++i)
dims[i] = iter.next();
return dims;
}
/**
* <p>Indicates if a netCDF variable is a coordinate variable (i.e. has only
* one netCDF dimension and that dimension has the same name).</p>
*
* <p>This implementation uses {@link #isNumeric(Variable)}.</p>
*
* @param var A netCDF variable.
* @return <code>true</code> if and only if <code>
* var</code> is a coordinate variable.
*/
protected boolean isCoordinateVariable(Variable var)
{
if (var.getRank() != 1 || !isNumeric(var))
return false;
return getDimensions(var)[0].getName().equals(var.getName());
}
/**
* Returns an iterator over the virtual VisAD data objects of this view.
*
* @return An iterator over the virtual VisAD data objects
* of the view.
*/
public VirtualDataIterator getVirtualDataIterator()
{
return new DataIterator();
}
/**
* <p>Indicates if a given variable should be ignored by the {@link
* VirtualDataIterator} during iteration over the virtual VisAD data objects
* in the netCDF dataset.</p>
*
* @return <code>true</code> if and only if the variable
* should be ignored.
*/
protected abstract boolean isIgnorable(Variable var);
/**
* Returns the domain of a netCDF variable.
*
* @param var A netCDF variable.
* @return The domain of the netCDF variable.
* @throws IllegalArgumentException
* if the rank of the variable is zero.
* @throws TypeException if a {@link RealType} needed to be created but
* couldn't.
* @throws IOException if a netCDF read-error occurs.
*/
protected abstract Domain getDomain(Variable var)
throws TypeException, IOException;
/**
* <p>Returns the virtual VisAD data object corresponding to a named netCDF
* variable.</p>
*
* <p>This implementation uses {@link #getData(Variable)}.</p>
*
* @param name The name of the netCDF variable.
* @return The corresponding virtual VisAD data object.
* @throws NullPointerException if the argument is <code>null</code>.
* @throws IllegalArgumentException
* if the netCDF variable doesn't exist.
* @throws TypeException if a {@link RealType} needed to be created
* but couldn't.
* @throws VisADException if a VisAD object couldn't be created.
* @throws IOException if a netCDF read-error occurs.
*/
public VirtualData getData(String name)
throws TypeException, VisADException, IOException
{
if (name == null)
throw new NullPointerException();
return getData(netcdf.get(name));
}
/**
* <p>Returns the virtual VisAD data object corresponding to a netCDF
* variable.</p>
*
* <p>This implementation uses {@link #getRealType(Variable)}, {@link
* #getRangeSet(Variable)}, {@link #getUnitFromAttribute(Variable)}, {@link
* #getVetter(Variable)}, and {@link #getDomain(Variable)}.<p>
*
* @param var The netCDF variable.
* @return The corresponding virtual VisAD data object.
* @throws NullPointerException if the argument is <code>null</code>.
* @throws TypeException if a {@link RealType} needed to be created
* but couldn't.
* @throws VisADException if a VisAD object couldn't be created.
* @throws IOException if a netCDF read-error occurs.
*/
protected VirtualData getData(Variable var)
throws TypeException, VisADException, IOException
{
VirtualScalar scalar =
(isNumeric(var) == true)
? (VirtualScalar)
new VirtualReal(getRealType(var),
var,
getRangeSet(var),
getUnitFromAttribute(var),
getVetter(var))
: (VirtualScalar)
new VirtualText(getTextType(var), var);
return
(var.getRank() == 0 || (!isNumeric(var) && var.getRank() == 1))
? (VirtualData)scalar
: getDomain(var).getVirtualField(
new VirtualTuple(scalar));
}
/**
* Iterates over the virtual VisAD data objects of this view.
*/
protected class DataIterator
extends VirtualDataIterator
{
/**
* The netCDF variable iterator.
*/
private final VariableIterator varIter;
/**
* Constructs from nothing.
*/
DataIterator()
{
super(View.this);
varIter = View.this.getNetcdf().iterator();
}
/**
* <p>Returns a copy of the next virtual VisAD data object.</p>
*
* <p>This implementation uses {@link #isCharToText()},
* {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)},
* and {@link #getData(Variable)}.</p>
*
* @return A copy of the next virtual VisAD data
* object or <code> null</code> if there is
* no more data.
* @throws TypeException if a {@link ScalarType} needed
* to be created but couldn't.
* @throws VisADException Couldn't create necessary VisAD object.
* @throws IOException if a netCDF read-error occurs.
*/
protected VirtualData getData()
throws TypeException, VisADException, IOException
{
while (varIter.hasNext())
{
Variable var = varIter.next();
// handle text only if charToText == true and rank <= 2
if (!isNumeric(var) && (!isCharToText() || var.getRank() > 2))
continue; // TODO: support arrays of text (Tuple?)
if (isIgnorable(var))
continue; // ignore what's ignorable
return View.this.getData(var);
}
return null; // no more data
}
}
/**
* The convention-dependent domain of a netCDF variable.
*/
protected abstract class Domain
{
/**
* Constructs from a netCDF variable.
*
* @throws NullPointerException if the argument is
* <code>null</code>.
* @throws IllegalArgumentException if the rank of the variable is 0.
*/
protected Domain(Variable var)
{
if (var.getRank() == 0)
throw new IllegalArgumentException(var.toString());
}
/**
* Returns a {@link VirtualField} corresponding to this domain and
* a given range.
*
* @param range The range for the {@link VirtualField}.
* @throws NullPointerException if the argument is <code>null</code>.
* @throws IOException if a netCDF read-error occurs.
* @throws VisADException if a VisAD object can't be created.
*/
protected abstract VirtualField getVirtualField(VirtualTuple range)
throws VisADException, IOException;
/**
* Indicates if this instance equals an object.
*
* @param obj The object to be compared against.
* @return <code>true</code> if and only if this
* instance equals the object.
*/
public abstract boolean equals(Object obj);
/**
* Returns the hash code of this instance.
*
* @return The hash code of this instance.
*/
public abstract int hashCode();
}
}