/*
* Geotools 2 - OpenSource mapping toolkit
* (C) 2003, Geotools Project Management Committee (PMC)
* (C) 2001, Institut de Recherche pour le Développement
*
* 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
*
*
* This package contains documentation from OpenGIS specifications.
* OpenGIS consortium's work is fully acknowledged here.
*/
package org.geotools.cv;
// J2SE dependencies
import java.awt.Color;
import java.awt.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.Serializable;
import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import javax.media.jai.JAI;
import org.geotools.ct.MathTransform1D;
import org.geotools.resources.ClassChanger;
import org.geotools.resources.RemoteProxy;
import org.geotools.resources.Utilities;
import org.geotools.resources.XArray;
import org.geotools.resources.XMath;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.VocabularyKeys;
import org.geotools.resources.i18n.Vocabulary;
import org.geotools.resources.image.ColorUtilities;
import org.geotools.units.Unit;
import org.geotools.util.NumberRange;
import org.opengis.cs.CS_Unit;
import org.opengis.cv.CV_ColorInterpretation;
import org.opengis.cv.CV_PaletteInterpretation;
import org.opengis.cv.CV_SampleDimension;
import org.opengis.cv.CV_SampleDimensionType;
import org.opengis.referencing.operation.TransformException;
/**
* Describes the data values for a coverage. For a grid coverage a sample dimension is a band.
* Sample values in a band may be organized in categories. This SampleDimension
* implementation is capable to differenciate qualitative and quantitative
* categories. For example an image of sea surface temperature (SST) could very well defines
* the following categories:
*
*
* * In this example, sample values in range* [0] : no data * [1] : cloud * [2] : land * [10..210] : temperature to be converted into Celsius degrees through a linear equation *
[10..210]
defines a quantitative category,
* while all others categories are qualitative. The difference between those two kinds of category
* is that the {@link Category#getSampleToGeophysics} method returns a non-null transform if and
* only if the category is quantitative.
*
* @source $URL$
* @version $Id$
* @author OpenGIS
* @author Martin Desruisseaux
*
* @see org.opengis.cv.CV_SampleDimension
*
* @deprecated Replaced by {@link org.geotools.coverage.GridSampleDimension}
* in the org.geotools.coverage
package.
*/
public class SampleDimension implements Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 6026936545776852758L;
/**
* A sample dimension wrapping the list of categories CategoryList.inverse
.
* This object is constructed and returned by {@link #geophysics}. Constructed when first
* needed, but serialized anyway because it may be a user-supplied object.
*/
private SampleDimension inverse;
/**
* The category list for this sample dimension, or null
if this sample
* dimension has no category. This field is read by SampleTranscoder
only.
*/
final CategoryList categories;
/**
* true
if all categories in this sample dimension have been already scaled
* to geophysics ranges. If true
, then the {@link #getSampleToGeophysics()}
* method should returns an identity transform. Note that the opposite do not always hold:
* an identity transform doesn't means that all categories are geophysics. For example,
* some qualitative categories may map to some values differents than NaN
.
* isGeophysics
== categories.isScaled(true)
.isGeophysics
!= categories.isScaled(false)
, except
* if categories.geophysics(true) == categories.geophysics(false)
true
if this sample dimension has at least one qualitative category.
* An arbitrary number of qualitative categories is allowed, providing their sample
* value ranges do not overlap. A sample dimension can have both qualitative and
* quantitative categories.
*/
private final boolean hasQualitative;
/**
* true
if this sample dimension has at least one quantitative category.
* An arbitrary number of quantitative categories is allowed, providing their sample
* value ranges do not overlap.
* sampleToGeophysics
is non-null, then hasQuantitative
* must be true. However, the opposite do not hold in all cases: a
* true
value doesn't means that sampleToGeophysics
should
* be non-null.
*/
private final boolean hasQuantitative;
/**
* The {@link Category#getSampleToGeophysics sampleToGeophysics} transform used by every
* quantitative {@link Category}, or null
. This field may be null for two
* reasons:
*
* null
. This constructor
* allows the construction of a SampleDimension
without explicit construction of
* {@link Category} objects. An heuristic approach is used for dispatching the informations
* into a set of {@link Category} objects. However, this constructor still less general and
* provides less fine-grain control than the constructor expecting an array of {@link Category}.
*
* @param description The sample dimension title or description, or null
if none.
* This is the value to be returned by {@link #getDescription}.
* @param type The grid value data type (which indicate the number of bits for the data type),
* or null
for computing it automatically from the range
* [minimum..maximum]
. This is the value to be returned by
* {@link #getSampleDimensionType}.
* @param color The color interpretation, or null
for a default value (usually
* {@link ColorInterpretation#PALETTE_INDEX PALETTE_INDEX}). This is the value to be
* returned by {@link #getColorInterpretation}.
* @param palette The color palette associated with the sample dimension, or null
* for a default color palette (usually grayscale). If categories
is
* non-null, then both arrays usually have the same length. However, this constructor
* is tolerant on this array length. This is the value to be returned (indirectly) by
* {@link #getColorModel}.
* @param categories A sequence of category names for the values contained in the sample
* dimension, or null
if none. This is the values to be returned by
* {@link #getCategoryNames}.
* @param nodata the values to indicate "no data", or null
if none. This is the
* values to be returned by {@link #getNoDataValue}.
* @param minimum The lower value, inclusive. The [minimum..maximum]
range may or
* may not includes the nodata
values; the range will be adjusted as
* needed. If categories
was non-null, then minimum
is
* usually 0. This is the value to be returned by {@link #getMinimumValue}.
* @param maximum The upper value, inclusive as well. The
* [minimum..maximum]
range may or may not includes the nodata
* values; the range will be adjusted as needed. If categories
was non-null,
* then maximum
is usually equals to categories.length-1
. This
* is the value to be returned by {@link #getMaximumValue}.
* @param scale The value which is multiplied to grid values, or 1 if none. This is the value
* to be returned by {@link #getScale}.
* @param offset The value to add to grid values, or 0 if none. This is the value to be
* returned by {@link #getOffset}.
* @param unit The unit information for this sample dimension, or null
if none.
* This is the value to be returned by {@link #getUnits}.
*
* @throws IllegalArgumentException if the range [minimum..maximum]
is not valid.
*/
public SampleDimension(final String description,
SampleDimensionType type,
ColorInterpretation color,
final Color [] palette,
final String[] categories,
final double[] nodata,
double minimum,
double maximum,
final double scale,
final double offset,
final Unit unit)
{
// TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
// ("Relax constraint on placement of this()/super() call in constructors").
this(list(description, type, color, palette, categories, nodata,
minimum, maximum, scale, offset, unit));
}
/** Constructs a list of categories. Used by constructors only. */
private static CategoryList list(final String description,
SampleDimensionType type,
ColorInterpretation color,
final Color [] palette,
final String[] categories,
final double[] nodata,
double minimum,
double maximum,
final double scale,
final double offset,
final Unit unit)
{
if (Double.isInfinite(minimum) || Double.isInfinite(maximum) || !(minimum < maximum)) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_RANGE_$2,
new Double(minimum), new Double(maximum)));
}
if (Double.isNaN(scale) || Double.isInfinite(scale) || scale==0) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_PARAMETER_$2,
"scale", new Double(scale)));
}
if (Double.isNaN(offset) || Double.isInfinite(offset)) {
throw new IllegalArgumentException(Errors.format(ErrorKeys.BAD_PARAMETER_$2,
"offset", new Double(offset)));
}
if (type == null) {
type = SampleDimensionType.getEnum(minimum, maximum);
}
if (color == null) {
color = ColorInterpretation.PALETTE_INDEX;
}
final int nameCount = (categories!=null) ? categories.length : 0;
final int nodataCount = (nodata !=null) ? nodata.length : 0;
final List categoryList = new ArrayList(nameCount + nodataCount + 2);
/*
* STEP 1 - Add a qualitative category for each 'nodata' value.
* NAME: Fetched from 'categories' if available, otherwise default to the value.
* COLOR: Fetched from 'palette' if available, otherwise use Category default.
*/
for (int i=0; inull
if no category has units.
* This unit apply to values obtained after the
* {@link #getSampleToGeophysics sampleToGeophysics} transformation.
* @throws IllegalArgumentException if categories
contains incompatible
* categories. If may be the case for example if two or more categories have
* overlapping ranges of sample values.
*/
public SampleDimension(Category[] categories, Unit units) throws IllegalArgumentException {
// TODO: 'list(...)' should be inlined there if only Sun was to fix RFE #4093999
// ("Relax constraint on placement of this()/super() call in constructors").
this(list(categories, units));
}
/** Construct a list of categories. Used by constructors only. */
private static CategoryList list(final Category[] categories, final Unit units) {
if (categories == null) {
return null;
}
CategoryList list = new CategoryList(categories, units);
list = (CategoryList) Category.pool.canonicalize(list);
if (CategoryList.isScaled(categories, false)) return list;
if (CategoryList.isScaled(categories, true )) return list.inverse;
throw new IllegalArgumentException(Errors.format(ErrorKeys.MIXED_CATEGORIES));
}
/**
* Constructs a sample dimension with the specified list of categories.
*
* @param list The list of categories, or null
.
*/
private SampleDimension(final CategoryList list) {
MathTransform1D main = null;
boolean isMainValid = true;
boolean qualitative = false;
if (list != null) {
for (int i=list.size(); --i>=0;) {
final MathTransform1D candidate = ((Category)list.get(i)).getSampleToGeophysics();
if (candidate == null) {
qualitative = true;
continue;
}
if (main != null) {
isMainValid &= main.equals(candidate);
}
main = candidate;
}
this.isGeophysics = list.isScaled(true);
} else {
this.isGeophysics = false;
}
this.categories = list;
this.hasQualitative = qualitative;
this.hasQuantitative = (main != null);
this.sampleToGeophysics = isMainValid ? main : null;
}
/**
* Constructs a new sample dimension with the same categories and
* units than the specified sample dimension.
*
* @param other The other sample dimension, or null
.
*/
protected SampleDimension(final SampleDimension other) {
if (other != null) {
inverse = other.inverse;
categories = other.categories;
isGeophysics = other.isGeophysics;
hasQualitative = other.hasQualitative;
hasQuantitative = other.hasQuantitative;
sampleToGeophysics = other.sampleToGeophysics;
} else {
// 'inverse' will be set when needed.
categories = null;
isGeophysics = false;
hasQualitative = false;
hasQuantitative = false;
sampleToGeophysics = null;
}
}
/**
* 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.
*/
public SampleDimensionType getSampleDimensionType() {
final NumberRange range = getRange();
if (range == null) {
return SampleDimensionType.FLOAT;
}
return SampleDimensionType.getEnum(range);
}
/**
* Get the sample dimension title or description.
* This string may be null
if no description is present.
*
* @param locale The locale, or null
for the default one.
* @return The localized description, or null
if none.
* If no description was available in the specified locale,
* then a default locale is used.
*
* @see CV_SampleDimension#getDescription()
*/
public String getDescription(final Locale locale) {
return (categories!=null) ? categories.getName(locale) : null;
}
/**
* Returns a sequence of category names for the values contained in this sample dimension.
* This allows for names to be assigned to numerical values. The first entry in the sequence
* relates to a cell value of zero. For example:
*
* * * @param locale The locale for category names, or* [0] Background * [1] Water * [2] Forest * [3] Urban *
null
for a default locale.
* @return The sequence of category names for the values contained in this sample dimension,
* or null
if there is no category in this sample dimension.
* @throws IllegalStateException if a sequence can't be mapped because some category use
* negative or non-integer sample values.
*
* @see CV_SampleDimension#getCategoryNames()
* @see #getCategories
* @see #getCategory
*/
public String[] getCategoryNames(final Locale locale) throws IllegalStateException {
if (categories == null) {
return null;
}
if (categories.isEmpty()) {
return new String[0];
}
String[] names = null;
for (int i=categories.size(); --i>=0;) {
final Category category = (Category) categories.get(i);
final int lower = (int) category.minimum;
final int upper = (int) category.maximum;
if (lower!=category.minimum || lower<0 ||
upper!=category.maximum || upper<0)
{
final Vocabulary resources = Vocabulary.getResources(locale);
throw new IllegalStateException(resources.getString(
ErrorKeys.NON_INTEGER_CATEGORY));
}
if (names == null) {
names = new String[upper+1];
}
Arrays.fill(names, lower, upper+1, category.getName(locale));
}
return names;
}
/**
* Returns all categories in this sample dimension. Note that a {@link Category} object may
* apply to an arbitrary range of sample values. Consequently, the first element in this
* collection may not be directly related to the sample value 0
.
*
* @return The list of categories in this sample dimension, or null
if none.
*
* @see #getCategoryNames
* @see #getCategory
*/
public List getCategories() {
return categories;
}
/**
* Returns the category for the specified sample value. If this method can't maps
* a category to the specified value, then it returns null
.
*
* @param sample The value (can be one of NaN
values).
* @return The category for the supplied value, or null
if none.
*
* @see #getCategories
* @see #getCategoryNames
*/
public Category getCategory(final double sample) {
return (categories!=null) ? categories.getCategory(sample) : null;
}
/**
* Returns a default category to use for background. A background category is used
* when an image is resampled (for
* example reprojected in an other coordinate system) and the resampled image do not
* fit in a rectangular area. It can also be used in various situation where a raisonable
* "no data" category is needed. The default implementation try to returns one
* of the {@linkplain #getNoDataValue no data values}. If no suitable category is found,
* then a {@linkplain Category#NODATA default} one is returned.
*
* @return A category to use as background for the "Resample" operation.
* Never null
.
*/
public Category getBackground() {
return (categories!=null) ? categories.nodata : Category.NODATA;
}
/**
* Returns the values to indicate "no data" for this sample dimension. The default
* implementation deduces the "no data" values from the list of categories supplied
* at construction time. The rules are:
*
* null
, then
* getNoDataValue()
returns null
as well.
* This means that this sample dimension contains no category or contains
* only qualitative categories (e.g. a band from a classified image).getNoDataValue()
returns null
.
* This means that sample value in this sample dimension are already
* expressed in geophysics values and that all "no data" values (if any)
* have already been converted into NaN
values.NaN
will be ignored.NaN
.
*
* @return The values to indicate no data values for this sample dimension,
* or null
if not applicable.
* @throws IllegalStateException if some qualitative categories use a range of
* non-integer values.
*
* @see CV_SampleDimension#getNoDataValue()
* @see #getSampleToGeophysics
*/
public double[] getNoDataValue() throws IllegalStateException {
if (!hasQuantitative) {
return null;
}
int count = 0;
double[] padValues = null;
final int size = categories.size();
for (int i=0; iNaN
values. A {@link NumberRange} object
* gives more informations than {@link #getMinimumValue} and {@link #getMaximumValue} methods
* since it contains also the data type (integer, float, etc.) and inclusion/exclusion
* informations.
*
* @return The range of values. May be null
if this sample dimension has no
* quantitative category.
*
* @see Category#getRange
* @see #getMinimumValue
* @see #getMaximumValue
*
* @task TODO: We should do a better job in CategoryList.getRange() when selecting
* the appropriate data type. SampleDimensionType.getEnum(Range) may be of
* some help.
*/
public NumberRange getRange() {
return (categories!=null) ? categories.getRange() : null;
}
/**
* Returns true
if at least one value of values
is
* in the range lower
inclusive to upper
exclusive.
*/
private static boolean rangeContains(final double lower,
final double upper,
final double[] values)
{
if (values != null) {
for (int i=0; ivalue
maps a qualitative category, then the
* category name is returned as of {@link Category#getName(Locale)}.value
maps a quantitative category, then the value is
* transformed into a geophysics value as with the {@link #getSampleToGeophysics()
* sampleToGeophysics} transform, the result is formatted as a number and the unit
* symbol is appened.NaN
values).
* @param locale Locale to use for formatting, or null
for the default locale.
* @return A string representation of the geophysics value, or null
if there is
* none.
*
* @task REVISIT: What should we do when the value can't be formatted?
* SampleDimension
returns null
if there is no
* category or if an exception is thrown, but CategoryList
* returns "Untitled" if the value is an unknow NaN, and try to format
* the number anyway in other cases.
*/
public String getLabel(final double value, final Locale locale) {
if (categories != null) {
if (isGeophysics) {
return categories.format(value, locale);
} else try {
return categories.inverse.format(categories.transform(value), locale);
} catch (TransformException exception) {
// Value probably don't match a category. Ignore...
}
}
return null;
}
/**
* Returns the unit information for this sample dimension.
* May returns null
if this dimension has no units.
* This unit apply to values obtained after the {@link #getSampleToGeophysics
* sampleToGeophysics} transformation.
*
* @see CV_SampleDimension#getUnits()
* @see #getSampleToGeophysics
*/
public Unit getUnits() {
return (categories!=null) ? categories.geophysics(true).getUnits() : null;
}
/**
* Returns the value to add to grid values for this sample dimension.
* This attribute is typically used when the sample dimension represents
* elevation data. The transformation equation is:
*
* * * Together with {@link #getScale()} and {@link #getNoDataValue()}, this method provides a * limited way to transform sample values into geophysics values. However, the recommended * way is to use the {@link #getSampleToGeophysics sampleToGeophysics} transform instead, * which is more general and take care of converting automatically "no data" values * intooffset + scale*sample
NaN
.
*
* @return The offset to add to grid values.
* @throws IllegalStateException if the transform from sample to geophysics values
* is not a linear relation.
*
* @see CV_SampleDimension#getOffset()
* @see #getSampleToGeophysics
* @see #rescale
*/
public double getOffset() throws IllegalStateException {
return getCoefficient(0);
}
/**
* Returns the value which is multiplied to grid values for this sample dimension.
* This attribute is typically used when the sample dimension represents elevation
* data. The transformation equation is:
*
* * * Together with {@link #getOffset()} and {@link #getNoDataValue()}, this method provides a * limited way to transform sample values into geophysics values. However, the recommended * way is to use the {@link #getSampleToGeophysics sampleToGeophysics} transform instead, * which is more general and take care of converting automatically "no data" values * intooffset + scale*sample
NaN
.
*
* @return The scale to multiply to grid value.
* @throws IllegalStateException if the transform from sample to geophysics values
* is not a linear relation.
*
* @see CV_SampleDimension#getScale()
* @see #getSampleToGeophysics
* @see #rescale
*/
public double getScale() {
return getCoefficient(1);
}
/**
* Returns a coefficient of the linear transform from sample to geophysics values.
*
* @param order The coefficient order (0 for the offset, or 1 for the scale factor,
* 2 if we were going to implement quadratic relation, 3 for cubic, etc.).
* @return The coefficient.
* @throws IllegalStateException if the transform from sample to geophysics values
* is not a linear relation.
*/
private double getCoefficient(final int order) throws IllegalStateException {
if (!hasQuantitative) {
// Default value for "offset" is 0; default value for "scale" is 1.
// This is equal to the order if 0 <= order <= 1.
return order;
}
Exception cause = null;
if (sampleToGeophysics != null) try {
final double value;
switch (order) {
case 0: value = sampleToGeophysics.transform(0); break;
case 1: value = sampleToGeophysics.derivative(Double.NaN); break;
default: throw new AssertionError(order); // Should not happen
}
if (!Double.isNaN(value)) {
return value;
}
} catch (TransformException exception) {
cause = exception;
}
IllegalStateException exception = new IllegalStateException(Errors.format(
ErrorKeys.NON_LINEAR_RELATION));
exception.initCause(cause);
throw exception;
}
/**
* Returns a transform from sample values to geophysics values. If this sample dimension
* has no category, then this method returns null
. If all sample values are
* already geophysics values (including NaN
for "no data" values), then this
* method returns an identity transform. Otherwise, this method returns a transform expecting
* sample values as input and computing geophysics value as output. This transform will take
* care of converting all "{@linkplain #getNoDataValue() no data values}" into
* NaN
values.
* The sampleToGeophysics.{@linkplain MathTransform1D#inverse() inverse()}
* transform is capable to differenciate NaN
values to get back the original
* sample value.
*
* @return The transform from sample to geophysics values, or null
if this
* sample dimension do not defines any transform (which is not the same that
* defining an identity transform).
*
* @see #getScale
* @see #getOffset
* @see #getNoDataValue
* @see #rescale
*/
public MathTransform1D getSampleToGeophysics() {
if (isGeophysics) {
return GeophysicsCategory.IDENTITY;
}
if (!hasQualitative && sampleToGeophysics!=null) {
// If there is only quantitative categories and they all use the same transform,
// then we don't need the indirection level provided by CategoryList.
return sampleToGeophysics;
}
// CategoryList is a MathTransform1D.
return categories;
}
/**
* If true
, returns the geophysics companion of this sample dimension. By
* definition, a geophysics sample dimension is a sample dimension with a
* {@linkplain #getRange range of sample values} transformed in such a way that the
* {@link #getSampleToGeophysics sampleToGeophysics} transform is always the identity
* transform, or null
if no such transform existed in the first place. In
* other words, the range of sample values in all category maps directly the "real world"
* values without the need for any transformation.
* SampleDimension
objects live by pair: a geophysics one (used for
* computation) and a non-geophysics one (used for packing data, usually as
* integers). The geo
argument specifies which object from the pair is wanted,
* regardless if this method is invoked on the geophysics or non-geophysics instance of the
* pair. In other words, the result of geophysics(b1).geophysics(b2).geophysics(b3)
* depends only on the value in the last call (b3
).
*
* @param geo true
to get a sample dimension with an identity
* {@linkplain #getSampleToGeophysics transform} and a {@linkplain #getRange range of
* sample values} matching the geophysics values, or false
to get back the
* original sample dimension.
* @return The sample dimension. Never null
, but may be this
.
*
* @see Category#geophysics
* @see org.geotools.gc.GridCoverage#geophysics
*/
public SampleDimension geophysics(final boolean geo) {
if (geo == isGeophysics) {
return this;
}
if (inverse == null) {
if (categories != null) {
inverse = new SampleDimension(categories.inverse);
inverse.inverse = this;
} else {
/*
* If there is no categories, then there is no real difference between
* "geophysics" and "indexed" sample dimensions. Both kinds of sample
* dimensions would be identical objects, so we are better to just
* returns 'this'.
*/
inverse = this;
}
}
return inverse;
}
// NOTE: "getPaletteInterpretation()" is not available in Geotools since
// palette are backed by IndexColorModel, which support only RGB.
/**
* Returns the color interpretation of the sample dimension.
* A sample dimension can be an index into a color palette or be a color model
* component. If the sample dimension is not assigned a color interpretation
* the value is {@link ColorInterpretation#UNDEFINED}.
*
* @see CV_SampleDimension#getColorInterpretation()
*/
public ColorInterpretation getColorInterpretation() {
// The 'GridSampleDimension' class overrides this method
// with better values for 'band' and 'numBands' constants.
final int band = 0;
final int numBands = 1;
return ColorInterpretation.getEnum(getColorModel(band, numBands), band);
}
/**
* Returns a color model for this sample dimension. The default implementation create a color
* model with 1 band using each category's colors as returned by {@link Category#getColors}.
* The returned color model will typically use data type {@link DataBuffer#TYPE_FLOAT} if this
* SampleDimension
instance is "geophysics", or an integer data type otherwise.
* SampleDimension
. In this particular case, the color model
* created by this getColorModel()
method will have the same number of bands
* than the grid coverage's {@link RenderedImage}.
*
* @return The requested color model, suitable for {@link RenderedImage} objects with values
* in the {@link #getRange}
range. May be null
if this
* sample dimension has no category.
*/
public ColorModel getColorModel() {
// The 'GridSampleDimension' class overrides this method
// with better values for 'band' and 'numBands' constants.
final int band = 0;
final int numBands = 1;
return getColorModel(band, numBands);
}
/**
* Returns a color model for this sample dimension. The default implementation create the
* color model using each category's colors as returned by {@link Category#getColors}. The
* returned color model will typically use data type {@link DataBuffer#TYPE_FLOAT} if this
* SampleDimension
instance is "geophysics", or an integer data type otherwise.
*
* @param visibleBand The band to be made visible (usually 0). All other bands, if any
* will be ignored.
* @param numBands The number of bands for the color model (usually 1). The returned color
* model will renderer only the visibleBand
and ignore the others, but
* the existence of all numBands
will be at least tolerated. Supplemental
* bands, even invisible, are useful for processing with Java Advanced Imaging.
* @return The requested color model, suitable for {@link RenderedImage} objects with values
* in the {@link #getRange}
range. May be null
if this
* sample dimension has no category.
*
* @task REVISIT: This method may be deprecated in a future version. It it strange to use
* only one SampleDimension
for creating a multi-bands color
* model. Logically, we would expect as many SampleDimension
s
* as bands.
*/
public ColorModel getColorModel(final int visibleBand, final int numBands) {
if (categories != null) {
return categories.getColorModel(visibleBand, numBands);
}
return null;
}
/**
* Returns a color model for this sample dimension. The default implementation create the
* color model using each category's colors as returned by {@link Category#getColors}.
*
* @param visibleBand The band to be made visible (usually 0). All other bands, if any
* will be ignored.
* @param numBands The number of bands for the color model (usually 1). The returned color
* model will renderer only the visibleBand
and ignore the others, but
* the existence of all numBands
will be at least tolerated. Supplemental
* bands, even invisible, are useful for processing with Java Advanced Imaging.
* @param type The data type that has to be used for the sample model
* @return The requested color model, suitable for {@link RenderedImage} objects with values
* in the {@link #getRange}
range. May be null
if this
* sample dimension has no category.
*
* @task REVISIT: This method may be deprecated in a future version. It it strange to use
* only one SampleDimension
for creating a multi-bands color
* model. Logically, we would expect as many SampleDimension
s
* as bands.
*/
public ColorModel getColorModel(final int visibleBand, final int numBands, final int type) {
if (categories != null) {
return categories.getColorModel(visibleBand, numBands, type);
}
return null;
}
/**
* Returns a sample dimension using new {@link #getScale scale} and {@link #getOffset offset}
* coefficients. Other properties like the {@linkplain #getRange sample value range},
* {@linkplain #getNoDataValue no data values} and {@linkplain #getColorModel colors}
* are unchanged.
*
* @param scale The value which is multiplied to grid values for the new sample dimension.
* @param offset The value to add to grid values for the new sample dimension.
*
* @see #getScale
* @see #getOffset
* @see Category#rescale
*/
public SampleDimension rescale(final double scale, final double offset) {
final MathTransform1D sampleToGeophysics = Category.createLinearTransform(scale, offset);
final Category[] categories = (Category[]) getCategories().toArray();
final Category[] reference = (Category[]) categories.clone();
for (int i=0; iCategoryList
.
* For now, we assume that people using the GCS package probably want to work
* with {@link org.geotools.gc.GridCoverage}, which make extensive use of JAI.
* Peoples just working with {@link org.geotools.cv.Coverage} are stuck with
* the overhead. Note that we register the image operation here because the
* only operation's argument is of type SampleDimension[]
.
* Consequently, the image operation may be invoked at any time after class
* loading of {@link SampleDimension}.
* META-INF/registryFile.jai
file may not be the best idea neithter,
* since peoples using JAI without the GCS module may be stuck with the overhead
* of loading GCS classes.
*/
static {
SampleTranscoder.register(JAI.getDefaultInstance());
}
/////////////////////////////////////////////////////////////////////////
//////////////// ////////////////
//////////////// OPENGIS ADAPTER ////////////////
//////////////// ////////////////
/////////////////////////////////////////////////////////////////////////
/**
* Returns an OpenGIS interface for this sample dimension. This method first
* looks in the cache. If no interface was previously cached, then this
* method creates a new adapter and caches the result.
*
* @param adapters The originating {@link Adapters}.
* @return The OpenGIS interface. The returned type is a generic {@link Object}
* in order to avoid premature class loading of OpenGIS interface.
* @throws RemoteException if this object can't be exported.
*/
final synchronized Object toOpenGIS(final Object adapters) throws RemoteException {
if (proxy != null) {
if (proxy instanceof Reference) {
final Object ref = ((Reference) proxy).get();
if (ref != null) {
return ref;
}
} else {
return proxy;
}
}
final Object opengis = new Export(adapters);
proxy = new WeakReference(opengis);
return opengis;
}
/**
* Wraps a {@link SampleDimension} object for use with OpenGIS. This wrapper is a
* good place to check for non-implemented OpenGIS methods (just check for methods
* throwing {@link UnsupportedOperationException}). This class is suitable for RMI
* use.
*/
final class Export extends UnicastRemoteObject implements CV_SampleDimension, RemoteProxy {
/**
* The originating adapter.
*/
private final Adapters adapters;
/**
* Constructs a remote object.
*/
protected Export(final Object adapters) throws RemoteException {
super(); // TODO: Fetch the port number from the adapter.
this.adapters = (Adapters)adapters;
}
/**
* Returns the underlying implementation.
*/
public final Serializable getImplementation() throws RemoteException {
return SampleDimension.this;
}
/**
* Sample dimension title or description.
*/
public String getDescription() throws RemoteException {
return SampleDimension.this.getDescription(null);
}
/**
* A code value indicating grid value data type.
*
* @task TODO: We should get this information by inspecting
* the image's underlying {@link SampleModel}.
*/
public CV_SampleDimensionType getSampleDimensionType() throws RemoteException {
return adapters.export(SampleDimension.this.getSampleDimensionType());
}
/**
* Sequence of category names for the values contained in a sample dimension.
*/
public String[] getCategoryNames() throws RemoteException {
return SampleDimension.this.getCategoryNames(null);
}
/**
* Color interpretation of the sample dimension.
*/
public CV_ColorInterpretation getColorInterpretation() throws RemoteException {
return adapters.export(SampleDimension.this.getColorInterpretation());
}
/**
* Indicates the type of color palette entry for sample dimensions which have a palette.
*/
public CV_PaletteInterpretation getPaletteInterpretation() throws RemoteException {
return new CV_PaletteInterpretation(CV_PaletteInterpretation.CV_RGB);
}
/**
* Color palette associated with the sample dimension.
*/
public int[][] getPalette() throws RemoteException {
final ColorModel model = getColorModel();
if (model instanceof IndexColorModel) {
final IndexColorModel index = (IndexColorModel) model;
final int[][] palette = new int[index.getMapSize()][];
final boolean hasAlpha = index.hasAlpha();
for (int i=0; i