/*
* Copyright 1998, University Corporation for Atmospheric Research
* All Rights Reserved.
* See file LICENSE for copying and redistribution conditions.
*
* $Id: DefaultView.java,v 1.9 2004-11-19 23:31:08 donm Exp $
*/
package visad.data.netcdf.in;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import java.util.WeakHashMap;
import ucar.netcdf.Dimension;
import ucar.netcdf.Netcdf;
import ucar.netcdf.Variable;
import ucar.netcdf.VariableIterator;
import visad.data.netcdf.QuantityDB;
import visad.CoordinateSystem;
import visad.ErrorEstimate;
import visad.Gridded1DSet;
import visad.GriddedSet;
import visad.Integer1DSet;
import visad.IntegerNDSet;
import visad.Linear1DSet;
import visad.LinearLatLonSet;
import visad.LinearNDSet;
import visad.LinearSet;
import visad.MathType;
import visad.RealTupleType;
import visad.RealType;
import visad.SampledSet;
import visad.SetType;
import visad.TextType;
import visad.TypeException;
import visad.Unit;
import visad.VisADException;
/**
* Provides support for the default view of a netCDF dataset as documented
* in the netCDF User's Guide.
*
* @author Steven R. Emmerson
* @version $Revision: 1.9 $ $Date: 2004-11-19 23:31:08 $
*/
public class DefaultView
extends View
{
private final DimsToSet dimsToSet;
/**
* Constructs from a netCDF dataset and a quantity database.
*
* @param netcdf The netCDF dataset.
* @param quantityDB The quantity database to use to map netCDF
* variables to VisAD Quantity-s.
*/
public DefaultView(Netcdf netcdf, QuantityDB quantityDB)
{
this(netcdf, quantityDB, false);
}
/**
* Constructs from a netCDF dataset and a quantity database.
*
* @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
*/
public DefaultView(Netcdf netcdf, QuantityDB quantityDB, boolean charToText)
{
super(netcdf, quantityDB, charToText);
dimsToSet = new DimsToSet();
}
/**
* <p>Indicates if a given variable should be ignored during iteration.</p>
*
* <p>This implementation returns the value of {@link
* #isCoordinateVariable(Variable)}.</p>
*
* @return <code>true</code> if and only if the variable
* should be ignored.
*/
protected boolean isIgnorable(Variable var)
{
return isCoordinateVariable(var);
}
/**
* Returns the domain of a netCDF variable.
*
* @param var A netCDF variable.
* @return The domain of the given 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 Domain getDomain(Variable var)
throws TypeException, IOException
{
return new DefaultDomain(var);
}
/**
* <p>Returns the VisAD domain set corresponding to an array of netCDF
* dimension in netCDF order (outermost dimension first).</p>
*
* <p>This implementation supports 1-dimensional coordinate variables,
* 1-dimensional longitude, and 2-dimensional latitude/longitude domains
* and assumes that each netCDF dimension is independent of all others.
* This implementation uses {@link #getDomainSet(Dimension)}, {@link
* #getDomainSet(Dimension)}, and {@link #getDomainType(Dimension[])}. </p>
*
* @param dims A netCDF domain. Must be in netCDF order
* (outer dimension first).
* @return The VisAD domain set corresponding to
* <code>dims</code>.
* @throws VisADException Couldn't create necessary VisAD object.
* @exception IOException if a netCDF read-error occurs.
*/
protected SampledSet getDomainSet(Dimension[] dims)
throws IOException, VisADException
{
if (dims.length == 0)
return getDomainSet(dims[0]);
/*
* This implementation caches earlier results because this operation
* is potentially expensive and multiple netCDF variables can have the
* same domain.
*/
GriddedSet set;
synchronized(dimsToSet)
{
set = dimsToSet.get(dims);
if (set == null)
{
Gridded1DSet[] sets = new Gridded1DSet[dims.length];
int j = dims.length;
for (int i = 0; i < dims.length; ++i)
sets[--j] = getDomainSet(dims[i]); // reverse order
boolean allInteger1DSets = true;
for (int i = 0; allInteger1DSets && i < sets.length; ++i)
allInteger1DSets = sets[i] instanceof Integer1DSet;
MathType type = getDomainType(dims);
if (allInteger1DSets)
{
set = (GriddedSet)getIntegerSet(sets, type);
}
else
{
boolean allLinear1DSets = true;
for (int i = 0; allLinear1DSets && i < sets.length; ++i)
allLinear1DSets = sets[i] instanceof Linear1DSet;
if (allLinear1DSets)
{
set = (GriddedSet)getLinearSet(sets, type);
}
else
{
set = getGriddedSet(sets, type);
}
}
dimsToSet.put(dims, set);
}
}
return set;
}
/**
* Returns an {@link IntegerSet} corresponding to the product of one or more
* {@link Integer1DSet}s.
*
* @param sets The {@link Integer1DSet}s to be multiplied
* together.
* @param type The {@link MathType} for the result.
* @return The {@link IntegerSet} corresponding to the
* input.
* @throws VisADException if a VisAD object couldn't be created.
*/
private static GriddedSet getIntegerSet(Gridded1DSet[] sets, MathType type)
throws VisADException
{
int rank = sets.length;
int[] lengths = new int[rank];
for (int idim = 0; idim < rank; ++idim)
lengths[idim] = ((Integer1DSet)sets[idim]).getLength(0);
// TODO: add CoordinateSystem argument
return IntegerNDSet.create(type, lengths, (CoordinateSystem)null,
(Unit[])null, (ErrorEstimate[])null);
}
/**
* <p>Returns a {@link LinearSet} corresponding to the product of one or
* more {@link Linear1DSet}s.</p>
*
* <p>This implementation uses {@link #isLongitude(RealType)} and {@link
* #isLatitude(RealType)}.
*
* @param sets The {@link Linear1DSet}s to be multiplied
* together.
* @param type The {@link MathType} for the result.
* NB: The units in the sets needn't be the
* same as the units in <code>type</code>.
* @return The {@link LinearSet} corresponding to the
* input.
* @throws VisADException if a VisAD object couldn't be created.
*/
private LinearSet getLinearSet(Gridded1DSet[] sets, MathType type)
throws VisADException
{
LinearSet set = null;
int rank = sets.length;
double[] firsts = new double[rank];
double[] lasts = new double[rank];
int[] lengths = new int[rank];
Unit[] units = new Unit[rank];
for (int idim = 0; idim < rank; ++idim)
{
Linear1DSet linear1DSet = (Linear1DSet)sets[idim];
firsts[idim] = linear1DSet.getFirst();
lengths[idim] = linear1DSet.getLength(0);
lasts[idim] = linear1DSet.getLast();
units[idim] = linear1DSet.getSetUnits()[0];
}
// TODO: add CoordinateSystem argument
if (rank == 2)
{
RealType[] types = ((RealTupleType)type).getRealComponents();
if ((isLongitude(types[0]) && isLatitude(types[1])) ||
(isLongitude(types[1]) && isLatitude(types[0])))
{
set = new LinearLatLonSet(type,
firsts[0], lasts[0], lengths[0],
firsts[1], lasts[1], lengths[1],
(CoordinateSystem)null,
units,
(ErrorEstimate[])null);
}
}
if (set == null)
{
set = LinearNDSet.create(type,
firsts, lasts, lengths,
(CoordinateSystem)null,
units,
(ErrorEstimate[])null);
}
return set;
}
/**
* Returns a {@link GriddedSet} corresponding to one or more {@link
* Gridded1DSet}s.
*
* @param sets The {@link Gridded1DSet}s to be multiplied
* together.
* @param type The {@link MathType} for the result.
* NB: The units in the sets needn't be the
* same as the units in <code>type</code>.
* @return The {@link GriddedSet} corresponding to the
* input.
* @throws VisADException if a VisAD object couldn't be created.
* @throws IOException if a netCDF read-error occurs.
*/
private static GriddedSet getGriddedSet(Gridded1DSet[] sets, MathType type)
throws VisADException, IOException
{
int rank = sets.length;
// Handle the case where we only have one set. Save us some work
// and keep the integrity of a Gridded1DDoubleSet for Time
if (rank == 1 && sets[0].getType().equals(new SetType(type))) {
return sets[0];
}
int[] lengths = new int[rank];
float[][] values = new float[rank][];
int ntotal = 1;
for (int idim = 0; idim < rank; ++idim)
{
lengths[idim] = sets[idim].getLength(0);
ntotal *= lengths[idim];
}
int step = 1;
int laststep = 1;
for (int idim = 0; idim < rank; ++idim)
{
float[] vals = sets[idim].getSamples(false)[0];
values[idim] = new float[ntotal];
step *= lengths[idim];
for (int i=0; i<lengths[idim]; i++) {
int istep = i * laststep;
for (int j=0; j<ntotal; j+=step) {
for (int k=0; k<laststep; k++) {
values[idim][istep+j+k] = vals[i];
}
}
}
laststep = step;
}
Unit[] units = new Unit[rank];
for (int idim = 0; idim < rank; ++idim)
units[idim] = sets[idim].getSetUnits()[0];
// TODO: add CoordinateSystem argument
return GriddedSet.create(type, values, lengths,
(CoordinateSystem)null, units, (ErrorEstimate[])null);
}
/**
* <p>Returns the VisAD {@link MathType} corresponding to an array of netCDF
* dimensions. If the array has zero length, then <code>null</code> is
* returned.</p>
*
* <p>This implementation uses {@link #getRealType(Dimension)}.</p>
*
* @param dims netCDF dimensions in netCDF order (outermost
* dimension first).
* @return The type of the domain corresponding to
* <code>dims</code>. RETURN_VALUE is
* <code>null</code>, a <code>RealType</code>,
* or a <code>RealTupleType</code> if
* <code>dims.length</code> is 0, 1, or greater
* than 1, respectively.
* @throws VisADException Couldn't create necessary VisAD object.
*/
protected MathType getDomainType(Dimension[] dims)
throws VisADException
{
MathType type;
int rank = dims.length;
if (rank == 0)
{
type = null; // means scalar domain
}
else if ( rank == 1)
{
type = (MathType)getRealType(dims[0]);
}
else
{
RealType[] types = new RealType[dims.length];
int j = dims.length;
for (int i = 0; i < dims.length; ++i)
types[--j] = getRealType(dims[i]); // reverse order
type = new RealTupleType(types);
}
return type;
}
/**
* Iterates over the virtual VisAD data objects in a netCDF dataset.
*/
final class DefaultDataIterator
extends VirtualDataIterator
{
/**
* The netCDF variable iterator.
*/
private final VariableIterator varIter;
/**
* Constructs from nothing.
*/
DefaultDataIterator()
{
super(DefaultView.this);
varIter = DefaultView.this.getNetcdf().iterator();
}
/**
* Returns a clone of the next virtual VisAD data object.
*
* <p>This implementation uses {@link #isCharToText()},
* {@link #isNumeric(Variable)}, {@link #isIgnorable(Variable)},
* and {@link #getData(Variable)}.</p>
*
* @return A clone 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.
*/
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 ignorable variables
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));
}
return null; // no more data
}
}
/**
* The default domain of a netCDF variable. A default domain comprises
* the variable's netCDF dimensions.
*/
private final class DefaultDomain
extends Domain
{
/**
* Outermost dimension first; at least one element.
*/
private final Dimension[] dims;
private volatile int hashCode;
private volatile SampledSet domainSet;
/**
* @throws IllegalArgumentException if the rank of the variable is 0.
*/
DefaultDomain(Variable var)
throws TypeException
{
super(var);
dims = getDimensions(var);
}
/**
* 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 read error occurs.
* @throws VisADException if a VisAD object can't be created.
*/
protected VirtualField getVirtualField(VirtualTuple range)
throws VisADException, IOException
{
VirtualField field;
int rank = dims.length;
if (rank == 2 && range.getType() instanceof TextType) { // char
field =
VirtualField.newVirtualField(
DefaultView.this.getDomainSet(dims[0]), range);
}
else if (rank == 1 || !DefaultView.this.isTime(dims[0]))
{
field =
VirtualField.newVirtualField(
DefaultView.this.getDomainSet(dims), range);
}
else
{
Dimension[] innerDims = new Dimension[rank-1];
System.arraycopy(dims, 1, innerDims, 0, innerDims.length);
field =
VirtualField.newVirtualField(
DefaultView.this.getDomainSet(dims[0]),
new VirtualTuple(
VirtualField.newVirtualField(
DefaultView.this.getDomainSet(innerDims),
range)));
}
return field;
}
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (!(obj instanceof DefaultDomain))
return false;
return Arrays.equals(dims, ((DefaultDomain)obj).dims);
}
public int hashCode()
{
int hash = hashCode;
if (hash == 0)
{
hash = 1;
for (int i = 0; i < dims.length; i++)
hash = hash*31 + dims[i].hashCode();
hashCode = hash;
}
return hash;
}
}
/**
* Cache of netCDF dimensions and their corresponding VisAD domain sets.
*/
private static class DimsToSet
{
private Map map;
DimsToSet()
{
map = Collections.synchronizedMap(new WeakHashMap());
}
void put(Dimension[] dims, GriddedSet set)
{
if (dims.length == 1)
map.put(dims[0], set);
else
map.put(new DimArray(dims), set);
}
GriddedSet get(Dimension[] dims)
{
return
dims.length == 1
? (GriddedSet)map.get(dims[0])
: (GriddedSet)map.get(new DimArray(dims));
}
private static class DimArray
{
private Dimension[] dims;
private volatile int hashCode;
DimArray(Dimension[] dims)
{
this.dims = (Dimension[])dims.clone(); // defensive copy
}
public boolean equals(Object obj)
{
if (obj == this)
return true;
if (!(obj instanceof DimArray))
return false;
return Arrays.equals(dims, ((DimArray)obj).dims);
}
public int hashCode()
{
int hash = hashCode;
if (hash == 0)
{
hash = 1;
for (int i = 0; i < dims.length; i++)
hash = hash*31 + dims[i].hashCode();
hashCode = hash;
}
return hash;
}
}
}
}