/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2001-2012, Open Source Geospatial Foundation (OSGeo)
* (C) 2009-2012, Geomatys
*
* 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.geotoolkit.coverage;
import java.awt.Color;
import org.opengis.referencing.operation.MathTransform1D;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.InternationalString;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.geotoolkit.resources.Errors;
import org.apache.sis.measure.NumberRange;
/**
* A "geophysics" view of a category. Sample values in this category are equal to geophysics
* values. By definition, the {@link #getSampleToGeophysics} method for this class returns
* the identity transform, or {@code null} if this category is a qualitative one.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @since 2.0
* @module
*/
final class GeophysicsCategory extends Category {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -7164422654831370784L;
/**
* Creates a new instance of geophysics category.
*
* @param inverse The originating {@link Category}.
* @param isQuantitative {@code true} if the originating category is quantitative.
* @throws TransformException if a transformation failed.
*/
GeophysicsCategory(Category inverse, boolean isQuantitative) throws TransformException {
super(inverse, isQuantitative);
}
/**
* Returns the category name localized in the specified locale.
*/
@Override
public InternationalString getName() {
assert !(inverse instanceof GeophysicsCategory);
return inverse.getName();
}
/**
* Returns the set of colors for this category.
*/
@Override
public Color[] getColors() {
assert !(inverse instanceof GeophysicsCategory);
return inverse.getColors();
}
/**
* Returns the range of geophysics value.
*
* @return The range of geophysics values.
* @throws IllegalStateException if sample values can't be transformed into geophysics values.
*
* @todo The algorithm for finding minimum and maximum values is very simple for
* now and will not work if the transformation has local extremas. We would
* need some more sophesticated algorithm for the most general cases. Such
* a general algorithm would be useful in the super-class constructor as well.
*/
@Override
public NumberRange<?> getRange() throws IllegalStateException {
NumberRange<?> range = this.range;
if (range == null) try {
final MathTransform1D tr = inverse.transform;
final NumberRange<?> r = inverse.range;
boolean minIncluded = r.isMinIncluded();
boolean maxIncluded = r.isMaxIncluded();
double min = tr.transform(r.getMinDouble());
double max = tr.transform(r.getMaxDouble());
double min2 = tr.transform(r.getMinDouble(!minIncluded));
double max2 = tr.transform(r.getMaxDouble(!maxIncluded));
if ((minIncluded ? min2 : min) > (maxIncluded ? max2 : max)) {
final double tmp, tmp2;
final boolean tmpIncluded;
tmp=min; tmp2=min2; tmpIncluded=minIncluded;
min=max; min2=max2; minIncluded=maxIncluded;
max=tmp; max2=tmp2; maxIncluded=tmpIncluded;
}
assert Double.doubleToLongBits(minimum) == Double.doubleToLongBits(minIncluded ? min : min2);
assert Double.doubleToLongBits(maximum) == Double.doubleToLongBits(maxIncluded ? max : max2);
this.range = range = new Range(min, minIncluded, max, maxIncluded, min2, max2);
} catch (TransformException cause) {
throw new IllegalStateException(Errors.format(Errors.Keys.IllegalTransformForType_1,
inverse.transform.getClass()), cause);
}
return range;
}
/**
* Returns a transform from sample values to geophysics values, which (by definition)
* is an identity transformation. If this category is not a quantitative one, then
* this method returns {@code null}.
*/
@Override
public MathTransform1D getSampleToGeophysics() {
return isQuantitative() ? (MathTransform1D) MathTransforms.identity(1) : null;
}
/**
* Returns {@code true} if this category is quantitative.
*/
@Override
public boolean isQuantitative() {
assert !(inverse instanceof GeophysicsCategory) : inverse;
return inverse.isQuantitative();
}
/**
* Returns a new category for the same range of sample values but a different color palette.
*/
@Override
public Category recolor(final Color[] colors) {
assert !(inverse instanceof GeophysicsCategory) : inverse;
return inverse.recolor(colors).inverse;
}
/**
* Changes the mapping from sample to geophysics values.
*/
@Override
public Category rescale(MathTransform1D sampleToGeophysics) {
if (sampleToGeophysics.isIdentity()) {
return this;
}
sampleToGeophysics = MathTransforms.concatenate(
inverse.getSampleToGeophysics(), sampleToGeophysics);
return inverse.rescale(sampleToGeophysics).inverse;
}
/**
* If {@code false}, returns a category with the original sample values.
*/
@Override
public Category geophysics(final boolean geo) {
// Assertion below is for preventing recursive invocation.
assert !(inverse instanceof GeophysicsCategory) : inverse;
return inverse.geophysics(geo);
}
/**
* Range of geophysics values computed from the range of the {@linkplain #inverse indexed
* category}. The {@code inverse.transform} transformation is used for computing the
* inclusive and exclusive minimum and maximum values of this range. We compute both the
* inclusive and exclusive values because we can't rely on the default implementation, which
* looks for the nearest representable number. For example is the range of index values is 0
* to 10 exclusive (or 0 to 9 inclusive) and the scale is 2, then the range of geophysics
* values is 0 to 20 exclusive or 0 to 18 inclusive, not 0 to 19.9999... The numbers between
* 18 and 20 is a "gray area" where we don't know for sure what the user intend to do.
*
* @author Martin Desruisseaux (IRD)
* @version 3.00
*
* @see GeophysicsCategory#getRange
*
* @since 2.1
* @module
*/
private static final class Range extends NumberRange<Double> {
/**
* Serial number for inter-operability with different versions.
*/
private static final long serialVersionUID = -1416908614729956928L;
/**
* The minimal value to be returned by {@link #getMinimum(boolean)} when the
* {@code inclusive} flag is the opposite of {@link #isMinIncluded()}.
*/
private final double minimum2;
/**
* The maximal value to be returned by {@link #getMaximum(boolean)} when the
* {@code inclusive} flag is the opposite of {@link #isMaxIncluded()}.
*/
private final double maximum2;
/**
* Constructs a range of {@code double} values.
*/
public Range(final double minimum, final boolean isMinIncluded,
final double maximum, final boolean isMaxIncluded,
final double minimum2, final double maximum2)
{
super(Double.class, minimum, isMinIncluded, maximum, isMaxIncluded);
this.minimum2 = minimum2;
this.maximum2 = maximum2;
}
/**
* Returns the minimum value with the specified inclusive or exclusive state.
*/
@Override
public double getMinDouble(final boolean inclusive) {
return (inclusive == isMinIncluded()) ? getMinDouble() : minimum2;
}
/**
* Returns the maximum value with the specified inclusive or exclusive state.
*/
@Override
public double getMaxDouble(final boolean inclusive) {
return (inclusive == isMaxIncluded()) ? getMaxDouble() : maximum2;
}
}
}