/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2015, 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.coverage.grid;
import java.awt.Color;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.Raster;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import javax.measure.unit.Unit;
import javax.media.jai.iterator.RectIter;
import javax.media.jai.iterator.RectIterFactory;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.factory.Hints;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.NumberRange;
import org.geotools.util.SimpleInternationalString;
import org.opengis.coverage.ColorInterpretation;
import org.opengis.coverage.SampleDimensionType;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.util.InternationalString;
/**
* Describes the band values for a grid coverage.
*
* @since 2.1
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
final class RenderedSampleDimension extends GridSampleDimension {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 946331925096804779L;
/**
* Band number for this sample dimension.
*/
private final int band;
/**
* The number of bands in the {@link GridCoverage} who own this sample dimension.
*/
private final int numBands;
/**
* The grid value data type.
*/
private final SampleDimensionType type;
/**
* Constructs a sample dimension with a set of categories from an other sample dimension.
*
* @param band The originating sample dimension.
* @param image The image to be wrapped by {@link GridCoverage}.
* @param bandNumber The band number.
*/
private RenderedSampleDimension(final GridSampleDimension band,
final RenderedImage image,
final int bandNumber)
{
super(band);
final SampleModel model = image.getSampleModel();
this.band = bandNumber;
this.numBands = model.getNumBands();
this.type = TypeMap.getSampleDimensionType(model, bandNumber);
}
/**
* Creates a set of sample dimensions for the given image. The array length of both
* arguments must matches the number of bands in the supplied {@code image}.
*
* @param name The name for data (e.g. "Elevation").
* @param image The image for which to create a set of sample dimensions.
* @param src User-provided sample dimensions, or {@code null} if none.
* @param dst The array where to put sample dimensions.
* @return {@code true} if all sample dimensions are geophysics (quantitative), or
* {@code false} if all sample dimensions are non-geophysics (qualitative).
* @throws IllegalArgumentException if geophysics and non-geophysics dimensions are mixed.
*/
static boolean create(final CharSequence name,
final RenderedImage image,
final GridSampleDimension[] src,
final GridSampleDimension[] dst)
{
final int numBands = image.getSampleModel().getNumBands();
if (src!=null && src.length!=numBands) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, src.length, "SampleDimension"));
}
if (dst.length != numBands) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, dst.length, "SampleDimension"));
}
/*
* Now, we know that the number of bands and the array length are consistent.
* Search if there is any null SampleDimension. If any, replace the null value
* by a default SampleDimension.
*/
int count = 0;
GridSampleDimension[] defaultSD = null;
for (int i=0; i<numBands; i++) {
GridSampleDimension sd = (src!=null) ? src[i] : null;
if (sd == null) {
/*
* If the user didn't provided explicitly a SampleDimension, create a default one.
* We will creates a SampleDimension for all bands in one step, even if only a few
* of them are required.
*/
if (defaultSD == null) {
defaultSD = new GridSampleDimension[numBands];
create(name, RectIterFactory.create(image, null), image.getSampleModel(),
null, null, null, null, defaultSD, null);
}
sd = defaultSD[i];
}
sd = new RenderedSampleDimension(sd, image, i);
dst[i] = sd;
count++;
}
if (count == numBands) {
return true;
}
throw new IllegalArgumentException(Errors.format(ErrorKeys.MIXED_CATEGORIES));
}
/**
* Creates a set of sample dimensions for the given raster.
*
* @param name The name for data (e.g. "Elevation").
* @param raster The raster.
* @param min The minimal value for each bands, or {@code null} for computing it automatically.
* @param max The maximal value for each bands, or {@code null} for computing it automatically.
* @param units The units of sample values, or {@code null} if unknow.
* @param colors The colors to use for values from {@code min} to {@code max} for each
* bands, or {@code null} for a default color palette. If non-null, each arrays
* {@code colors[b]} may have any length; colors will be interpolated as needed.
* @param hints An optional set of rendering hints, or {@code null} if none. Those hints will
* not affect the sample dimensions to be created. The optional hint
* {@link Hints#SAMPLE_DIMENSION_TYPE} specifies the {@link SampleDimensionType}
* to be used at rendering time, which can be one of
* {@link SampleDimensionType#UBYTE UBYTE} or
* {@link SampleDimensionType#USHORT USHORT}.
* @return The sample dimension for the given raster.
*/
static GridSampleDimension[] create(final CharSequence name,
final Raster raster,
final double[] min,
final double[] max,
final Unit<?> units,
final Color[][] colors,
final RenderingHints hints)
{
final GridSampleDimension[] dst = new GridSampleDimension[raster.getNumBands()];
create(name, (min==null || max==null) ? RectIterFactory.create(raster, null) : null,
raster.getSampleModel(), min, max, units, colors, dst, hints);
return dst;
}
/**
* Creates a set of sample dimensions for the data backing the given iterator.
*
* @param name The name for data (e.g. "Elevation").
* @param iterator The iterator through the raster data, or {@code null}.
* @param model The image or raster sample model.
* @param min The minimal value, or {@code null} for computing it automatically.
* @param max The maximal value, or {@code null} for computing it automatically.
* @param units The units of sample values, or {@code null} if unknow.
* @param colors The colors to use for values from {@code min} to {@code max} for each bands,
* or {@code null} for a default color palette. If non-null, each arrays
* {@code colors[b]} may have any length; colors will be interpolated as needed.
* @param dst The array where to store sample dimensions. The array length must matches
* the number of bands.
* @param hints An optional set of rendering hints, or {@code null} if none.
* Those hints will not affect the sample dimensions to be created. The optional hint
* {@link Hints#SAMPLE_DIMENSION_TYPE} specifies the {@link SampleDimensionType}
* to be used at rendering time, which can be one of
* {@link SampleDimensionType#UBYTE UBYTE} or
* {@link SampleDimensionType#USHORT USHORT}.
*/
private static void create(final CharSequence name,
final RectIter iterator,
final SampleModel model,
double[] min,
double[] max,
final Unit<?> units,
final Color[][] colors,
final GridSampleDimension[] dst,
final RenderingHints hints)
{
final int numBands = dst.length;
if (min!=null && min.length != numBands) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, min.length, "min[i]"));
}
if (max!=null && max.length != numBands) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, max.length, "max[i]"));
}
if (colors!=null && colors.length != numBands) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3,
numBands, colors.length, "colors[i]"));
}
/*
* Arguments are know to be valids. We now need to compute two ranges:
*
* STEP 1: Range of target (sample) values. This is computed in the following block.
* STEP 2: Range of source (geophysics) values. It will be computed one block later.
*
* The target (sample) values will typically range from 0 to 255 or 0 to 65535, but the
* general case is handled as well. If the source raster uses floating point
* numbers, then a "nodata" category may be added in order to handle NaN values. If the
* source raster use integer numbers instead, then we will rescale samples only if they
* would not fit in the target data type.
*/
final SampleDimensionType sourceType = TypeMap.getSampleDimensionType(model, 0);
final boolean sourceIsFloat = TypeMap.isFloatingPoint(sourceType);
SampleDimensionType targetType = null;
if (hints != null) {
targetType = (SampleDimensionType) hints.get(Hints.SAMPLE_DIMENSION_TYPE);
}
if (targetType == null) {
// Default to TYPE_BYTE for floating point images only; otherwise keep unchanged.
targetType = sourceType;
}
// Default setting: no scaling
NumberRange targetRange = TypeMap.getRange(targetType);
Category[] categories = new Category[1];
/*
* Now, constructs the sample dimensions. We will inconditionnaly provides a "nodata"
* category for floating point images targeting unsigned integers, since we don't know
* if the user plan to have NaN values. Even if the current image doesn't have NaN values,
* it could have NaN later if the image uses a writable raster.
*/
final InternationalString n = SimpleInternationalString.wrap(name);
NumberRange sourceRange = TypeMap.getRange(sourceType);
for (int b=0; b<numBands; b++) {
final Color[] c = colors!=null ? colors[b] : null;
categories[0] = new Category(n, c, targetRange, true);
dst[b] = new GridSampleDimension(name,categories, units);
}
}
/**
* Returns a code value indicating grid value data type.
* This will also indicate the number of bits for the data type.
*
* @return a code value indicating grid value data type.
*/
@Override
public SampleDimensionType getSampleDimensionType() {
return type;
}
/**
* Returns the color interpretation of the sample dimension.
*/
@Override
public ColorInterpretation getColorInterpretation() {
return TypeMap.getColorInterpretation(getColorModel(), band);
}
/**
* Returns a color model for this sample dimension.
*/
@Override
public ColorModel getColorModel() {
return getColorModel(band, numBands);
}
/**
* Returns the minimum value occurring in this sample dimension.
*/
// public double getMinimumValue()
// {return getHistogram().getLowValue(band);}
/**
* Returns the maximum value occurring in this sample dimension.
*/
// public double getMaximumValue()
// {return getHistogram().getHighValue(band);}
/**
* Determine the mode grid value in this sample dimension.
*/
// public double getModeValue()
// {throw new UnsupportedOperationException("Not implemented");}
/**
* Determine the median grid value in this sample dimension.
*/
// public double getMedianValue()
// {throw new UnsupportedOperationException("Not implemented");}
/**
* Determine the mean grid value in this sample dimension.
*/
// public double getMeanValue()
// {return getHistogram().getMean()[band];}
/**
* Determine the standard deviation from the mean
* of the grid values in a sample dimension.
*/
// public double getStandardDeviation()
// {return getHistogram().getStandardDeviation()[band];}
/**
* Gets the histogram for the underlying grid coverage.
*/
// private Histogram getHistogram()
// {throw new UnsupportedOperationException("Not implemented");}
}