/*
* 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.image.ColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.RasterFormatException;
import java.awt.image.RenderedImage;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.AbstractList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Locale;
import javax.media.jai.iterator.WritableRectIter;
import org.geotools.ct.MathTransform;
import org.geotools.ct.MathTransform1D;
import org.geotools.pt.CoordinatePoint;
import org.geotools.pt.Matrix;
import org.geotools.resources.Utilities;
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.units.Unit;
import org.geotools.util.NumberRange;
import org.opengis.referencing.operation.TransformException;
import org.opengis.spatialschema.geometry.MismatchedDimensionException;
/**
* An immutable list of categories. Categories are sorted by their sample values.
* Overlapping ranges of sample values are not allowed. A CategoryList
can
* contains a mix of qualitative and quantitative categories. The {@link #getCategory}
* method is responsible for finding the right category for an arbitrary sample value.
*
* Instances of {@link CategoryList} are immutable and thread-safe.
*
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux
*
* @deprecated Replaced by {@link org.geotools.coverage.CategoryList}
* in the org.geotools.coverage
package.
*/
class CategoryList extends AbstractList implements MathTransform1D, Comparator, Serializable {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = 2647846361059903365L;
/**
* The inverse transform, never null
.
* The following rule must hold:
*
*
this
is an instance of {@link CategoryList}, then
* inverse
must be an instance of {@link GeophysicsCategoryList}.this
is an instance of {@link GeophysicsCategoryList}, then
* inverse
must be an instance of {@link CategoryList}.NaN
values. This field will be computed
* only when first requested.
*/
private transient NumberRange range;
/**
* List of {@link Category#minimum} values for each category in {@link #categories}.
* This array must be in increasing order. Actually, this is the
* need to sort this array that determines the element order in {@link #categories}.
*/
private final double[] minimums;
/**
* The list of categories to use for decoding samples. This list most be sorted
* in increasing order of {@link Category#minimum}. This {@link CategoryList}
* object may be used as a {@link Comparator} for that. Qualitative categories
* (with NaN values) are last.
*/
private final Category[] categories;
/**
* The "main" category, or null
if there is none. The main category
* is the quantitative category with the widest range of sample values.
*/
private final Category main;
/**
* The "nodata" category (never null
). The "nodata" category is a
* category mapping the geophysics {@link Double#NaN} value. If none has been
* found, a default "nodata" category is used. This category is used to transform
* geophysics values to sample values into rasters when no suitable category has
* been found for a given geophysics value.
*/
final Category nodata;
/**
* The category to use if {@link #getCategory(double)} is invoked with a sample value
* greater than all sample ranges in this category list. This is usually a reference to
* the last category to have a range of real values. A null
value means
* that no fallback should be used. By extension, a null
value also means
* that {@link #getCategory} should not try to find any fallback at all if the requested
* sample value do not falls in a category range.
*/
private final Category overflowFallback;
/**
* The last used category. We assume that this category is the most likely
* to be requested in the next transform(...)
invocation.
*/
private transient Category last;
/**
* true
if there is gaps between categories, or false
otherwise.
* A gap is found if for example the range of value is [-9999 .. -9999] for the first
* category and [0 .. 1000] for the second one.
*/
private final boolean hasGaps;
/**
* Construct a category list using the specified array of categories.
*
* @param categories The list of categories.
* @param units The geophysics unit, or null
if none.
* @throws IllegalArgumentException if two or more categories
* have overlapping sample value range.
*/
public CategoryList(final Category[] categories, final Unit units)
throws IllegalArgumentException
{
this(categories, units, false, null);
assert isScaled(false);
}
/**
* Construct a category list using the specified array of categories.
*
* This constructor is for internal use only
*
* It is not private only because {@link GeophysicsCategoryList} need this constructor.
*
* @param categories The list of categories.
* @param units The geophysics unit, or null
if none.
* @param searchNearest The policy when {@link #getCategory} doesn't find an exact match
* for a sample value. true
means that it should search for the nearest
* category, while false
means that it should returns null
.
* @param inverse The inverse transform, or null
to build it automatically.
* This argument can be non-null only if invoked from
* {@link GeophysicsCategoryList} constructor.
* @throws IllegalArgumentException if two or more categories have overlapping sample value
* range.
*/
CategoryList(Category[] categories, Unit units, boolean searchNearest, CategoryList inverse)
throws IllegalArgumentException
{
/*
* Check if we are constructing a geophysics category list, then rescale all cagegories
* according. We may loose the user intend by doing so (he may have specified explicitly
* a list of GeophysicsCategory), but this is the SampleDimension's job to keep trace of
* it.
*/
final boolean isGeophysics = (this instanceof GeophysicsCategoryList);
assert (inverse != null) == isGeophysics;
this.categories = categories = (Category[]) categories.clone();
for (int i=0; idouble
. Cette méthode
* est similaire à {@link Double#compare(double,double)}, excepté
* qu'elle ordonne aussi les différentes valeurs NaN.
*/
private static int compare(final double v1, final double v2) {
if (Double.isNaN(v1) && Double.isNaN(v2)) {
final long bits1 = Double.doubleToRawLongBits(v1);
final long bits2 = Double.doubleToRawLongBits(v2);
if (bits1 < bits2) return -1;
if (bits1 > bits2) return +1;
}
return Double.compare(v1, v2);
}
/**
* Vérifie si le tableau de catégories spécifié est bien en ordre croissant.
* La comparaison ne tient pas compte des valeurs NaN
. Cette
* méthode n'est utilisée que pour les assert
.
*/
static boolean isSorted(final Category[] categories) {
for (int i=1; itrue
, returns a list of categories scaled
* to geophysics values. This method always returns a list of categories in which
* {@link Category#geophysics(boolean) Category.geophysics}(toGeophysics)
* has been invoked for each category.
*/
public CategoryList geophysics(final boolean toGeophysics) {
final CategoryList scaled = toGeophysics ? inverse : this;
assert scaled.isScaled(toGeophysics);
return scaled;
}
/**
* Verify if all categories are scaled to the specified state.
* This is used mostly in assertion statements.
*
* @param toGeophysics The state to test.
* @return true
if all categories are in the specified state.
*/
final boolean isScaled(final boolean toGeophysics) {
return isScaled(categories, toGeophysics);
}
/**
* Verify if all categories are scaled to the specified state.
*
* @param categories The categories to test.
* @param toGeophysics The state to test.
* @return true
if all categories are in the specified state.
*/
static boolean isScaled(final Category[] categories, final boolean toGeophysics) {
for (int i=0; inull
if there is no quantitative categories
* in this list, or if there is no unit information.
* null
since sample values are not geophysics
* values as long as they have not been transformed. The {@link SampleDimension}
* class will invoke geophysics(true).getUnits()
in order to get a
* non-null unit.
*/
public Unit getUnits() {
return null;
}
/**
* Returns the range of values in this category list. This is the union of the range
* of values of every categories, excluding NaN
values. A {@link NumberRange}
* object give more informations than {@link org.opengis.CV_SampleDimension#getMinimum}
* and {@link org.opengis.CV_SampleDimension#getMaximum} since it contains also the
* type (integer, float, etc.) and inclusion/exclusion informations.
*
* @return The range of values. May be null
if this category list has no
* quantitative category.
*
* @see Category#getRange
*/
public final NumberRange getRange() {
if (range == null) {
NumberRange range = null;
for (int i=0; igeophysics(true).format(...)
* anyway.
*
* @param value The value to format.
* @param writeUnit true
if unit symbol should be formatted after the number.
* Ignored if this category list has no unit.
* @param locale The locale, or null
for a default one.
* @param buffer The buffer where to format.
* @return The buffer buffer
for convenience.
*/
StringBuffer format(final double value, final boolean writeUnits,
final Locale locale, StringBuffer buffer)
{
return buffer.append(value);
}
/**
* Returns a color model for this category list. This method builds up the color model
* from 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.
* @return The requested color model, suitable for {@link RenderedImage} objects with values
* in the {@link #getRange}
range.
*/
public final ColorModel getColorModel(final int visibleBand, final int numBands) {
int type = DataBuffer.TYPE_FLOAT;
final NumberRange range = getRange();
final Class rt = range.getElementClass();
if (Byte.class.equals(rt) || Short.class.equals(rt) || Integer.class.equals(rt)) {
final int min = ((Number)range.getMinValue()).intValue();
final int max = ((Number)range.getMaxValue()).intValue();
if (min >= 0) {
if (max < 0x100) {
type = DataBuffer.TYPE_BYTE;
} else if (max < 0x10000) {
type = DataBuffer.TYPE_USHORT;
} else {
type = DataBuffer.TYPE_INT;
}
} else if (min >= Short.MIN_VALUE && max <= Short.MAX_VALUE) {
type = DataBuffer.TYPE_SHORT;
} else {
type = DataBuffer.TYPE_INT;
}
}
return ColorModelFactory.getColorModel(categories, type, visibleBand, numBands);
}
/**
* Returns a color model for this category list. This method builds up the color model
* from 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 transfer type used in the sample model
* @return The requested color model, suitable for {@link RenderedImage} objects with values
* in the {@link #getRange}
range.
*/
public final ColorModel getColorModel(final int visibleBand, final int numBands, final int type) {
return ColorModelFactory.getColorModel(categories, type, visibleBand, numBands);
}
/**
* Returns the category of the specified sample value.
* If no category fits, then this method returns null
.
*
* @param sample The value.
* @return The category of the supplied value, or null
.
*/
public final Category getCategory(final double sample) {
/*
* Recherche à quelle catégorie pourrait appartenir la valeur.
* Note: Les valeurs 'NaN' sont à la fin du tableau 'values'. Donc:
*
* 1) Si 'value' est NaN, alors 'i' pointera forcément sur une catégorie NaN.
* 2) Si 'value' est réel, alors 'i' peut pointer sur une des catégories de
* valeurs réels ou sur la première catégorie de NaN.
*/
int i = binarySearch(minimums, sample); // Special 'binarySearch' for NaN
if (i >= 0) {
// The value is exactly equals to one of Category.minimum,
// or is one of NaN values. There is nothing else to do.
assert Double.doubleToRawLongBits(sample) == Double.doubleToRawLongBits(minimums[i]);
return categories[i];
}
if (Double.isNaN(sample)) {
// The value is NaN, but not one of the registered ones.
// Consequently, we can't map a category to this value.
return null;
}
assert i == Arrays.binarySearch(minimums, sample) : i;
// 'binarySearch' found the index of "insertion point" (~i). This means that
// 'sample' is lower than 'Category.minimum' at this index. Consequently, if
// this value fits in a category's range, it fits in the previous category (~i-1).
i = ~i-1;
if (i >= 0) {
final Category category = categories[i];
assert sample > category.minimum : sample;
if (sample <= category.maximum) {
return category;
}
if (overflowFallback != null) {
if (++i < categories.length) {
final Category upper = categories[i];
// ASSERT: if 'upper.minimum' was smaller than 'value', it should has been
// found by 'binarySearch'. We use '!' in order to accept NaN values.
assert !(upper.minimum <= sample) : sample;
return (upper.minimum-sample < sample-category.maximum) ? upper : category;
}
return overflowFallback;
}
} else if (overflowFallback != null) {
// If the value is smaller than the smallest Category.minimum, returns
// the first category (except if there is only NaN categories).
if (categories.length != 0) {
final Category category = categories[0];
if (!Double.isNaN(category.minimum)) {
return category;
}
}
}
return null;
}
/**
* Format a sample value. If value
is a real number, then the value may
* be formatted with the appropriate number of digits and the units symbol. Otherwise,
* if value
is NaN
, then the category name is returned.
*
* @param value The sample value (may be NaN
).
* @param locale Locale to use for formatting, or null
for the default locale.
* @return A string representation of the sample value.
*/
public final String format(final double value, final Locale locale) {
if (Double.isNaN(value)) {
Category category = last;
if (!(value >= category.minimum && value <= category.maximum) &&
Double.doubleToRawLongBits(value) != Double.doubleToRawLongBits(category.minimum))
{
category = getCategory(value);
if (category == null) {
return Vocabulary.getResources(locale).getString(VocabularyKeys.UNTITLED);
}
last = category;
}
return category.getName(locale);
}
return format(value, true, locale, new StringBuffer()).toString();
}
//////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// I M P L E M E N T A T I O N O F List I N T E R F A C E ////////
//////// ////////
//////////////////////////////////////////////////////////////////////////////////////////
/**
* Returns the number of categories in this list.
*/
public final int size() {
return categories.length;
}
/**
* Returns the element at the specified position in this list.
*/
public final Object get(final int i) {
return categories[i];
}
/**
* Returns all categories in this CategoryList
.
*/
public final Object[] toArray() {
return (Category[]) categories.clone();
}
/**
* Returns a string representation of this category list.
* The returned string is implementation dependent.
* It is usually provided for debugging purposes only.
*/
public final String toString() {
return toString(this);
}
/**
* Returns a string representation of this category list.
* The owner
argument allow for a different
* class name to be formatted.
*/
final String toString(final Object owner) {
final String lineSeparator = System.getProperty("line.separator", "\n");
StringBuffer buffer = new StringBuffer(Utilities.getShortClassName(owner));
buffer = formatRange(buffer, null);
if (hasGaps) {
buffer.append(" with gaps");
}
buffer.append(lineSeparator);
/*
* Ecrit la liste des catégories en dessous.
*/
for (int i=0; iGeophysicsCategoryList
are not equal neither.
*/
private Object writeReplace() throws ObjectStreamException {
return Category.pool.canonicalize(this);
}
///////////////////////////////////////////////////////////////////////////////////////////////
//////// ////////
//////// I M P L E M E N T A T I O N O F MathTransform1D I N T E R F A C E ////////
//////// ////////
///////////////////////////////////////////////////////////////////////////////////////////////
/**
* Gets the dimension of input points, which is 1.
*/
public final int getDimSource() {
return 1;
}
/**
* Gets the dimension of output points, which is 1.
*/
public final int getDimTarget() {
return 1;
}
/**
* Tests whether this transform does not move any points.
*/
public boolean isIdentity() {
return false;
}
/**
* Returns the inverse transform of this object.
*/
public final MathTransform inverse() {
return inverse;
}
/**
* Ensure the specified point is one-dimensional.
*/
private static void checkDimension(final CoordinatePoint point) {
final int dim = point.getDimension();
if (dim != 1) {
throw new MismatchedDimensionException(Errors.format(
ErrorKeys.MISMATCHED_DIMENSION_$2,
new Integer(dim), new Integer(1)));
}
}
/**
* Transforms the specified ptSrc
and stores the result in ptDst
.
*/
public final CoordinatePoint transform(final CoordinatePoint ptSrc, CoordinatePoint ptDst)
throws TransformException
{
checkDimension(ptSrc);
if (ptDst==null) {
ptDst = new CoordinatePoint(1);
} else {
checkDimension(ptDst);
}
ptDst.ord[0] = transform(ptSrc.ord[0]);
return ptDst;
}
/**
* Gets the derivative of this transform at a point.
*/
public final Matrix derivative(final CoordinatePoint point) throws TransformException {
checkDimension(point);
return new Matrix(1, 1, new double[] {
derivative(point.ord[0])
});
}
/**
* Gets the derivative of this function at a value.
*
* @param value The value where to evaluate the derivative.
* @return The derivative at the specified point.
* @throws TransformException if the derivative can't be evaluated at the specified point.
*/
public final double derivative(final double value) throws TransformException {
Category category = last;
if (!(value >= category.minimum && value <= category.maximum) &&
Double.doubleToRawLongBits(value) != Double.doubleToRawLongBits(category.minimum))
{
category = getCategory(value);
if (category == null) {
throw new TransformException(Errors.format(
ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(value)));
}
last = category;
}
return category.transform.derivative(value);
}
/**
* Transforms the specified value.
*
* @param value The value to transform.
* @return the transformed value.
* @throws TransformException if the value can't be transformed.
*/
public final double transform(double value) throws TransformException {
Category category = last;
if (!(value >= category.minimum && value <= category.maximum) &&
Double.doubleToRawLongBits(value) != Double.doubleToRawLongBits(category.minimum))
{
category = getCategory(value);
if (category == null) {
throw new TransformException(Errors.format(
ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(value)));
}
last = category;
}
value = category.transform.transform(value);
if (overflowFallback != null) {
if (value < category.inverse.minimum) return category.inverse.minimum;
if (value > category.inverse.maximum) return category.inverse.maximum;
}
assert category == inverse.getCategory(value).inverse : category;
return value;
}
/**
* Transforms a list of coordinate point ordinal values. This implementation can work on
* either float or double arrays, since the quasi-totality of the implementation is the
* same. Locale variables still double
because this is the type used in
* {@link Category} objects.
*
* @task TODO: We could add an optimisation after the loops checking for category change:
* if we were allowed to search for nearest category (overflowFallback!=null),
* then make sure that the category really changed. There is already a slight
* optimization for the most common cases, but maybe we could go a little bit
* further.
*/
private void transform(final double[] srcPts, final float[] srcFloat, int srcOff,
final double[] dstPts, final float[] dstFloat, int dstOff,
int numPts, final boolean doublePrecision) throws TransformException
{
final int srcToDst = dstOff-srcOff;
Category category = last;
double maximum = category.maximum;
double minimum = category.minimum;
long rawBits = Double.doubleToRawLongBits(minimum);
final int direction;
if (srcPts!=dstPts || srcOff>=dstOff) {
direction = +1;
} else {
direction = -1;
dstOff += numPts-1;
srcOff += numPts-1;
}
/*
* Scan every points. Transforms will be performed by blocks, each time
* the loop detects that the category has changed. The break point is near
* the end of the loop, after we have done the transformation but before
* to change category.
*/
for (int peekOff=srcOff; true; peekOff += direction) {
// NOTE: We do not need to setup 'value' since we are not going to use it if
// numPts<0. Unfortunatly, the compiler flow analysis doesn't seem to
// be sophesticated enough to detect this case. So we have to set a dummy
// value in order to avoid compiler error.
double value = 0;
if (doublePrecision) { // Optimized loop for the 'double' version
while (--numPts >= 0) {
value = srcPts[peekOff];
if ((value>=minimum && value<=maximum) ||
Double.doubleToRawLongBits(value)==rawBits)
{
peekOff += direction;
continue;
}
break; // The category has changed. Stop the search.
}
} else {
while (--numPts >= 0) { // Optimized loop for the 'float' version
value = srcFloat[peekOff];
if ((value>=minimum && value<=maximum) ||
Double.doubleToRawLongBits(value)==rawBits)
{
peekOff += direction;
continue;
}
break; // The category has changed. Stop the search.
}
}
if (overflowFallback != null) {
// TODO: Slight optimization. We could go further by checking if 'value' is closer
// to this category than to the previous category or the next category. But
// we may need the category index, and binarySearch is a costly operation...
if (value > maximum && category==overflowFallback) {
continue;
}
if (value < minimum && category==categories[0]) {
continue;
}
}
/*
* The category has changed. Compute the start point (which depends of 'direction')
* and performs the transformation. If 'getCategory' was allowed to search for the
* nearest category, clamp all output values in their category range.
*/
int count = peekOff-srcOff; // May be negative if we are going backward.
if (count < 0) {
count = -count;
srcOff -= count-1;
}
if (doublePrecision) { // Optimized loop for the 'double' version.
category.transform.transform(srcPts, srcOff, dstPts, srcOff+srcToDst, count);
if (overflowFallback != null) {
dstOff = srcOff+srcToDst;
final double min = category.inverse.minimum;
final double max = category.inverse.maximum;
while (--count >= 0) { // Optimized loop for the 'double' version.
final double check = dstPts[dstOff];
if (check < min) {
dstPts[dstOff] = min;
} else if (check > max) {
dstPts[dstOff] = max;
}
dstOff++;
}
}
} else { // Optimized loop for the 'float' version.
category.transform.transform(srcFloat, srcOff, dstFloat, srcOff+srcToDst, count);
if (overflowFallback != null) {
dstOff = srcOff+srcToDst;
final float min = (float) category.inverse.minimum;
final float max = (float) category.inverse.maximum;
while (--count >= 0) { // Optimized loop for the 'double' version.
final float check = dstFloat[dstOff];
if (check < min) {
dstFloat[dstOff] = min;
} else if (check > max) {
dstFloat[dstOff] = max;
}
dstOff++;
}
}
}
/*
* Transformation is now finished for all points in the range [srcOff..peekOff]
* (not including 'peekOff'). If there is more points to examine, gets the new
* category for the next points.
*/
if (numPts < 0) {
break;
}
category = getCategory(value);
if (category == null) {
throw new TransformException(Errors.format(
ErrorKeys.NO_CATEGORY_FOR_VALUE_$1, new Double(value)));
}
maximum = category.maximum;
minimum = category.minimum;
rawBits = Double.doubleToRawLongBits(minimum);
srcOff = peekOff;
}
last = category;
}
/**
* Transforms a list of coordinate point ordinal values.
*/
public final void transform(double[] srcPts, int srcOff,
double[] dstPts, int dstOff, int numPts) throws TransformException
{
transform(srcPts, null, srcOff, dstPts, null, dstOff, numPts, true);
}
/**
* Transforms a list of coordinate point ordinal values.
*/
public final void transform(float[] srcPts, int srcOff,
float[] dstPts, int dstOff, int numPts) throws TransformException
{
transform(null, srcPts, srcOff, null, dstPts, dstOff, numPts, false);
}
/**
* Transform a raster. Only the current band in iterator
will be transformed.
* The transformed value are write back in the iterator
. If a different
* destination raster is wanted, a {@link org.geotools.resources.image.DualRectIter}
* may be used.
*
* @param iterator An iterator to iterate among the samples to transform.
* @throws RasterFormatException if a problem occurs during the transformation.
*/
public final void transform(final WritableRectIter iterator) throws RasterFormatException {
/*
* Category of the lowest minimum and highest maximum value (not including NaN),
* or null
=0;) {
if (!Double.isNaN(categories[i].maximum)) {
categoryMax = categories[i];
categoryMin = categories[0];
break;
}
}
Category category = main;
if (main == null) {
category = nodata;
}
double maximum = category.maximum;
double minimum = category.minimum;
long rawBits = Double.doubleToRawLongBits(minimum);
MathTransform1D tr = category.transform;
double maxTr, minTr;
if (overflowFallback == null) {
maxTr = Double.POSITIVE_INFINITY;
minTr = Double.NEGATIVE_INFINITY;
} else {
maxTr = category.inverse.maximum;
minTr = category.inverse.minimum;
}
try {
iterator.startLines();
if (!iterator.finishedLines()) do {
iterator.startPixels();
if (!iterator.finishedPixels()) do {
double value = iterator.getSampleDouble();
if (!(value>=minimum && value<=maximum) && // 'true' if value is NaN...
Double.doubleToRawLongBits(value) != rawBits) // and the NaN bits changed.
{
// Category has changed. Find the new category.
category = getCategory(value);
if (category == null) {
category = nodata;
}
maximum = (category!=categoryMax) ? category.maximum : Double.POSITIVE_INFINITY;
minimum = (category!=categoryMin) ? category.minimum : Double.NEGATIVE_INFINITY;
rawBits = Double.doubleToRawLongBits(minimum);
tr = category.transform;
if (overflowFallback != null) {
maxTr = category.inverse.maximum;
minTr = category.inverse.minimum;
}
}
/*
* TODO: This assertion fails in some circonstance: during conversions from
* geophysics to sample values and when the sample value is outside
* the inclusive range but inside the exclusive range... In this case
* 'getCategory(double)' may choose the wrong category. The fix would
* be to add new fiels in Category: we should have 'minInclusive' and
* 'minExclusive' instead of just 'minimum', and same for 'maximum'.
* The CategoryList.minimums array would still inclusive, but tests
* for range inclusion should use the exclusive extremas.
*/
assert hasGaps || (category==nodata) || // Disable assertion in those cases
(Double.isNaN(value) ? Double.doubleToRawLongBits(value) == rawBits
: (value>=minimum && value<=maximum)) : value;
value = tr.transform(value);
if (value > maxTr) {
value = maxTr;
} else if (value < minTr) {
value = minTr;
}
iterator.setSample(value);
}
while (!iterator.nextPixelDone());
}
while (!iterator.nextLineDone());
} catch (TransformException cause) {
RasterFormatException exception = new RasterFormatException(Errors.format(
ErrorKeys.BAD_TRANSFORM_$1, Utilities.getShortClassName(tr)));
exception.initCause(cause);
throw exception;
}
}
}