/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-2008, Open Source Geospatial Foundation (OSGeo)
*
* 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;
* version 2.1 of the License.
*
* 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.
*/
package org.geotools.image.io.netcdf;
import java.util.Map;
import java.util.Set;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import javax.imageio.ImageReader;
import ucar.nc2.dataset.AxisType;
import ucar.nc2.dataset.CoordinateAxis;
import ucar.nc2.dataset.CoordinateSystem;
import ucar.nc2.dataset.VariableEnhanced;
import org.geotools.image.io.GeographicImageReadParam;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
/**
* Default parameters for {@link NetcdfImageReader}.
*
* @since 2.4
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*
* @deprecated Having <cite>band dimension</cite> in this class is a problem because it make
* difficult to implement {@link NetcdfImageReader#getNumBands} in a reliable way.
* The information contained in this class need to move in some interface or in
* a {@code FileImageReaderND} superclass (common to NetCDF and HDF readers). The
* {@link AxisType} enumeration needs to be replaced by something neutral from GeoAPI.
*/
public class NetcdfReadParam extends GeographicImageReadParam {
/**
* The default source band to read from the NetCDF file. Also the default indice for
* any additional dimension after the one assigned to bands. We use the same default
* for consistency, because the third dimension (typically <var>z</var>) could be
* selected either by {@link #setSourceBands} or by {@link #setSliceIndice}; the
* relevant method depends on {@link NetcdfImageReader#getBandDimension} value.
*/
static final int DEFAULT_INDICE = 0;
/**
* The default source bands to read from the NetCDF file.
* Also the default destination bands in the buffered image.
*/
private static final int[] DEFAULT_BANDS = new int[] {DEFAULT_INDICE};
/**
* The types of the dimension to use as image bands. If more than one type is specified,
* only the first dimension with a suitable type will be assigned to bands.
*/
private Set<AxisType> bandDimensionTypes;
/**
* For <var>n</var>-dimensional images, the indices to use for dimensions above 2.
* Will be created only when first needed.
*/
private Map<AxisType,Integer> sliceIndices;
/**
* Creates a new, initially empty, set of parameters.
*
* @param reader The reader for which this parameter block is created
*/
public NetcdfReadParam(final ImageReader reader) {
super(reader);
setSourceBands (DEFAULT_BANDS);
setDestinationBands(DEFAULT_BANDS);
}
/**
* Returns {@code true} if there is some possibility that {@link #getBandDimension}
* returns a positive value.
*/
final boolean isBandDimensionSet() {
return (bandDimensionTypes != null) && !bandDimensionTypes.isEmpty() &&
NetcdfReadParam.class.equals(getClass());
// The last check is because the user could have overriden getBandDimension.
}
/**
* Returns the dimension to assign to bands for the specified variable. The default
* implementation returns the last dimension corresponding to one of the types specified
* to {@link #setBandDimensionTypes}. Users can override this method if the bands should
* be assigned from a dimension computed differently.
* <p>
* <b>Example:</b> For a NetCDF variable having dimensions in the
* (<var>t</var>,<var>z</var>,<var>y</var>,<var>x</var>) order (as in CF convention), if the
* {@linkplain AxisType#Height height} and {@linkplain AxisType#Pressure pressure} types have
* been {@linkplain #setBandDimensionTypes assigned} to bands, then this method will returns
* the index of the <var>z</var> dimension, i.e. {@code 1}.
*
* @param variable The variable for which we want to determine the dimension to assign to bands.
* @return The dimension assigned to bands, or {@code -1} if none.
*/
protected int getBandDimension(final VariableEnhanced variable) {
if (bandDimensionTypes != null) {
final List<CoordinateSystem> sys = variable.getCoordinateSystems();
if (sys != null) {
final int count = sys.size();
for (int i=0; i<count; i++) {
final CoordinateSystem cs = sys.get(i);
final List<CoordinateAxis> axes = cs.getCoordinateAxes();
if (axes != null) {
for (int j=axes.size(); --j>=0;) { // Must be reverse order; see javadoc
final CoordinateAxis axis = axes.get(j);
if (axis != null && bandDimensionTypes.contains(axis.getAxisType())) {
return j;
}
}
}
}
}
}
return -1;
}
/**
* Returns the dimension assigned to bands. This method returns the values given
* to the last call to {@link #setBandDimensionTypes}, or {@code null} if none.
*/
public AxisType[] getBandDimensionTypes() {
if (bandDimensionTypes == null) {
return null;
}
return bandDimensionTypes.toArray(new AxisType[bandDimensionTypes.size()]);
}
/**
* Assigns the dimension of the specified types to bands. For example in a NetCDF variable
* having (<var>t</var>,<var>y</var>,<var>x</var>) dimensions, it may be useful to treat
* the <var>t</var> dimension as bands. After invoking this method with the
* {@linkplain AxisType#Time time} value, users can select a time through the standard
* {@link #setSourceBands} API.
* <p>
* More than one type may be specified if they should be considered as synonymous. For example
* in order to assign the <var>z</var> dimension to bands, it may be necessary to specify both
* the {@linkplain AxisType#Height height} and {@linkplain AxisType#Pressure pressure} types.
*
* @param type The types of dimension to assign to bands.
*/
public void setBandDimensionTypes(final AxisType... types) {
if (types != null && types.length != 0) {
if (bandDimensionTypes == null) {
bandDimensionTypes = new HashSet<AxisType>();
} else {
bandDimensionTypes.clear();
}
for (int i=0; i<types.length; i++) {
bandDimensionTypes.add(types[i]);
}
} else {
bandDimensionTypes = null;
}
}
/**
* Returns the indice to set at the dimension of the specified axis. This is relevant only
* for <var>n</var>-dimensional data set where <var>n</var>>2. This method returns the
* last value set by {@link #setSliceIndice}.
*
* @param dimension The axis type (typically {@linkplain AxisType#Height height} or
* {@linkplain AxisType#Time time}).
* @return The indice to set at the dimension of the specified axis (0 by default).
*/
public int getSliceIndice(final AxisType axis) {
if (sliceIndices != null) {
final Integer indice = sliceIndices.get(axis);
if (indice != null) {
return indice.intValue();
}
}
return DEFAULT_INDICE;
}
/**
* Sets the indice for the dimension of the specified axis. This is relevant only for
* <var>n</var>-dimensional data set where <var>n</var>>2. For example in 4-D data
* set with (<var>x</var>,<var>y</var>,<var>z</var>,<var>t</var>) axis, those indices
* may be used by image readers for <var>z</var> and <var>t</var> dimensions.
* <p>
* The default value is 0 for all cases. This means that for the above-cited 4-D data set,
* only the image at the first time (<var>t</var>=0) and first altitude (<var>z</var>=0)
* is selected.
*
* @param dimension The axis type (typically {@linkplain AxisType#Height height} or
* {@linkplain AxisType#Time time}).
* @param indice The indice as a positive value.
*/
public void setSliceIndice(final AxisType dimension, final int indice) {
if (indice < 0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2, "indice", indice));
}
if (indice != DEFAULT_INDICE) {
if (sliceIndices == null) {
sliceIndices = new HashMap<AxisType,Integer>();
}
sliceIndices.put(dimension, indice);
} else if (sliceIndices != null) {
sliceIndices.remove(dimension);
if (sliceIndices.isEmpty()) {
sliceIndices = null; // hasNonNullIndices() wants that.
}
}
}
/**
* Returns {@code true} if this set of parameters contains a least one indice
* defined to a value different than 0.
*/
final boolean hasNonDefaultIndices() {
return sliceIndices != null;
}
}