/* * 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.metadata; import java.awt.image.DataBuffer; import org.geotools.util.NumberRange; import org.opengis.coverage.SampleDimension; /** * A {@code <SampleDimension>} element in * {@linkplain GeographicMetadataFormat geographic metadata format}. * * @since 2.4 * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux * * @see SampleDimension */ public class Band extends MetadataAccessor { /** * Creates a parser for a band. This constructor should not be invoked * directly; use {@link GeographicMetadata#getBand} instead. * * @param metadata The metadata which contains this band. * @param bandIndex The band index for this instance. */ protected Band(final GeographicMetadata metadata, final int bandIndex) { this(metadata.getBands(), bandIndex); } /** * Creates a parser for a band. This constructor should not be invoked * directly; use {@link GeographicMetadata#getBand} instead. * * @param parent The set of all bands. * @param bandIndex The band index for this instance. */ Band(final ChildList<Band> parent, final int bandIndex) { super(parent); selectChild(bandIndex); } /** * Returns the name for this band, or {@code null} if none. */ public String getName() { return getAttributeAsString("name"); } /** * Sets the name for this band. * * @param name The band name, or {@code null} if none. */ public void setName(final String name) { setAttributeAsString("name", name); } /** * Returns the range of valid values for this band. The range use the {@link Integer} * type if possible, or the {@link Double} type otherwise. Note that range * {@linkplain NumberRange#getMinValue minimum value}, * {@linkplain NumberRange#getMaxValue maximum value} or both may be null if no * {@code "minValue"} or {@code "maxValue"} attribute were found for the * {@code "SampleDimensions/SampleDimension"} element. */ public NumberRange getValidRange() { Number minimum, maximum; final boolean enabled = setWarningsEnabled(false); try { minimum = getAttributeAsInteger("minValue"); maximum = getAttributeAsInteger("maxValue"); } finally { setWarningsEnabled(enabled); } final Class<? extends Number> type; if (minimum == null || maximum == null) { minimum = getAttributeAsDouble("minValue"); maximum = getAttributeAsDouble("maxValue"); type = Double.class; } else { type = Integer.class; } // Note: minimum and/or maximum may be null, in which case the range in unbounded. return new NumberRange(type, minimum, true, maximum, true); } /** * Sets the range of valid values. The values should be integers most of the time since * they are packed values (often index in a color palette). But floating point values * are allowed too. * <p> * If the minimal or maximal value may be unknown, consider invoking * <code>{@link #setPackedValues setPackedValues}(minValue, maxValue, …)</code> * instead. The later can infers default bounds according a given data type. * * @param minValue The minimal valid <em>packed</em> value, * or {@link Double#NEGATIVE_INFINITY} if none. * @param maxValue The maximal valid <em>packed</em> value, * or {@link Double#POSITIVE_INFINITY} if none. * * @see #setPackedValues */ public void setValidRange(final double minValue, final double maxValue) { final int minIndex = (int) minValue; final int maxIndex = (int) maxValue; if (minIndex == minValue && maxIndex == maxValue) { setAttributeAsInteger("minValue", minIndex); setAttributeAsInteger("maxValue", maxIndex); } else { setAttributeAsDouble("minValue", minValue); setAttributeAsDouble("maxValue", maxValue); } } /** * Returns the fill values for this band, or {@code null} if none. */ public double[] getNoDataValues() { return getAttributeAsDoubles("fillValues", true); } /** * Sets the fill values for this band. This method formats all fill values as integers * if possible, or all values as floating points otherwise. We apply a "all or nothing" * rule for consistency. * * @param fillValues The packed values used for missing data, or {@code null} if none. * * @see #setPackedValues */ public void setNoDataValues(final double[] fillValues) { if (fillValues != null) { int[] asIntegers = new int[fillValues.length]; for (int i=0; i<fillValues.length; i++) { final double value = fillValues[i]; if ((asIntegers[i] = (int) value) != value) { asIntegers = null; // Not integers; stop the check. break; } } if (asIntegers != null) { setAttributeAsIntegers("fillValues", asIntegers); return; } } setAttributeAsDoubles("fillValues", fillValues); } /** * Defines valid and fill <em>packed</em> values as a combinaison of * <code>{@linkplain #setValidRange(double,double) setValidRange}(minValue, maxValue)</code> * and <code>{linkplain #setNoDataValues(double[]) setNoDataValues}(fillValues)</code>. * <p> * If the minimal or maximal value is {@linkplain Double#isInfinite infinite} and the data * type is an integer type, then this method replaces the infinite values by default bounds * inferred from the data type and the fill values. * * @param minValue The minimal valid <em>packed</em> value, * or {@link Double#NEGATIVE_INFINITY} if unknown. * @param maxValue The maximal valid <em>packed</em> value, * or {@link Double#POSITIVE_INFINITY} if unknown. * @param fillValues The packed values used for missing data, or {@code null} if none. * @param dataType The raw data type as one of {@link DataBuffer} constants, or * {@link DataBuffer#TYPE_UNDEFINED} if unknown. * * @see #setValidRange * @see #setNoDataValues */ public void setPackedValues(double minValue, double maxValue, final double[] fillValues, final int dataType) { minValue = replaceInfinity(minValue, fillValues, dataType); maxValue = replaceInfinity(maxValue, fillValues, dataType); setValidRange(minValue, maxValue); setNoDataValues(fillValues); } /** * If the specified value is infinity, then replace that values by a bounds inferred * from the specified fill values and data type. * * @param value The value. * @param fillValues The packed values used for missing data, or {@code null} if none. * @param dataType The raw data type as one of {@link DataBuffer} constants, or * {@link DataBuffer#TYPE_UNDEFINED} if unknown. */ private static double replaceInfinity(double value, final double[] fillValues, final int dataType) { final boolean negative; if (value == Double.NEGATIVE_INFINITY) { negative = true; } else if (value == Double.POSITIVE_INFINITY) { negative = false; } else { return value; } final double midValue; switch (dataType) { default: { // Unsigned integer: computes the upper bound according the data length. final long range = 1L << DataBuffer.getDataTypeSize(dataType); value = negative ? 0 : range - 1; midValue = range >>> 1; break; } case DataBuffer.TYPE_SHORT: { value = negative ? Short.MIN_VALUE : Short.MAX_VALUE; midValue = 0; break; } case DataBuffer.TYPE_INT: { value = negative ? Integer.MIN_VALUE : Integer.MAX_VALUE; midValue = 0; break; } case DataBuffer.TYPE_FLOAT: case DataBuffer.TYPE_DOUBLE: case DataBuffer.TYPE_UNDEFINED: { // Unbounded or undefined type: nothing to do. return value; } } /* * Considers only the fill values that are close to the bounds we just computed. We use * the middle value (always 0 for signed data type) as the threshold for choosing which * bounds is close to that value. In other words, for signed data type we consider only * positive or negative fill values (depending the 'value' sign), not both in same time. * * For each fill value to consider, reduces the range of valid values in such a way that * it doesn't include that fill value. The exclusion is performed by substracting 1, which * should be okay since we known at this stage that the data type is integer. */ if (fillValues != null) { double valueDistance = Math.abs(value - midValue); for (int i=0; i<fillValues.length; i++) { final double fillValue = fillValues[i]; if ((fillValue < midValue) == negative) { final double fillDistance = Math.abs(fillValue - midValue); if (fillDistance <= valueDistance) { valueDistance = fillDistance; value = fillValue - 1; // Value must be exclusive. } } } } return value; } /** * Returns the scale factor from packed to geophysics values, or {@code 1} if none. */ public double getScale() { final Double scale = getAttributeAsDouble("scale"); return (scale != null) ? scale.doubleValue() : 1.0; } /** * Sets the scale factor for this band. * * @param scale The scale from packed to geophysics values, or {@code 1} if none. */ public void setScale(final double scale) { setAttributeAsDouble("scale", scale); } /** * Returns the offset from packed to geophysics values, or {@code 0} if none. */ public double getOffset() { final Double offset = getAttributeAsDouble("offset"); return (offset != null) ? offset.doubleValue() : 0.0; } /** * Sets the offset for this band. * * @param offset The offset from packed to geophysics values, or {@code 0} if none. */ public void setOffset(final double offset) { setAttributeAsDouble("offset", offset); } }