001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2007, GeoTools Project Managment Committee (PMC)
005: * (C) 2007, Geomatys
006: *
007: * This library is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU Lesser General Public
009: * License as published by the Free Software Foundation;
010: * version 2.1 of the License.
011: *
012: * This library is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: */
017: package org.geotools.image.io.metadata;
018:
019: import java.awt.image.DataBuffer;
020: import org.geotools.util.NumberRange;
021:
022: /**
023: * A {@code <SampleDimension>} element in
024: * {@linkplain GeographicMetadataFormat geographic metadata format}.
025: *
026: * @since 2.4
027: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/unsupported/coverageio/src/main/java/org/geotools/image/io/metadata/Band.java $
028: * @version $Id: Band.java 27583 2007-10-23 11:29:26Z desruisseaux $
029: * @author Martin Desruisseaux
030: *
031: * @see org.opengis.coverage.SampleDimension
032: */
033: public class Band extends MetadataAccessor {
034: /**
035: * Creates a parser for a band. This constructor should not be invoked
036: * directly; use {@link GeographicMetadata#getBand} instead.
037: *
038: * @param metadata The metadata which contains this band.
039: * @param bandIndex The band index for this instance.
040: */
041: protected Band(final GeographicMetadata metadata,
042: final int bandIndex) {
043: this (metadata.getBands(), bandIndex);
044: }
045:
046: /**
047: * Creates a parser for a band. This constructor should not be invoked
048: * directly; use {@link GeographicMetadata#getBand} instead.
049: *
050: * @param parent The set of all bands.
051: * @param bandIndex The band index for this instance.
052: */
053: Band(final ChildList parent, final int bandIndex) {
054: super (parent);
055: selectChild(bandIndex);
056: }
057:
058: /**
059: * Returns the name for this band, or {@code null} if none.
060: */
061: public String getName() {
062: return getString("name");
063: }
064:
065: /**
066: * Sets the name for this band.
067: *
068: * @param name The band name, or {@code null} if none.
069: */
070: public void setName(final String name) {
071: setString("name", name);
072: }
073:
074: /**
075: * Returns the range of valid values for this band. The range use the {@link Integer}
076: * type if possible, or the {@link Double} type otherwise. Note that range
077: * {@linkplain NumberRange#getMinValue minimum value},
078: * {@linkplain NumberRange#getMaxValue maximum value} or both may be null if no
079: * {@code "minValue"} or {@code "maxValue"} attribute were found for the
080: * {@code "SampleDimensions/SampleDimension"} element.
081: */
082: public NumberRange getValidRange() {
083: Number minimum, maximum;
084: final boolean enabled = setWarningsEnabled(false);
085: try {
086: minimum = getInteger("minValue");
087: maximum = getInteger("maxValue");
088: } finally {
089: setWarningsEnabled(enabled);
090: }
091: final Class type;
092: if (minimum == null || maximum == null) {
093: minimum = getDouble("minValue");
094: maximum = getDouble("maxValue");
095: type = Double.class;
096: } else {
097: type = Integer.class;
098: }
099: // Note: minimum and/or maximum may be null, in which case the range in unbounded.
100: return new NumberRange(type, minimum, true, maximum, true);
101: }
102:
103: /**
104: * Sets the range of valid values. The values should be integers most of the time since
105: * they are packed values (often index in a color palette). But floating point values
106: * are allowed too.
107: * <p>
108: * If the minimal or maximal value may be unknown, consider invoking
109: * <code>{@link #setPackedValues setPackedValues}(minValue, maxValue, …)</code>
110: * instead. The later can infers default bounds according a given data type.
111: *
112: * @param minValue The minimal valid <em>packed</em> value,
113: * or {@link Double#NEGATIVE_INFINITY} if none.
114: * @param maxValue The maximal valid <em>packed</em> value,
115: * or {@link Double#POSITIVE_INFINITY} if none.
116: *
117: * @see #setPackedValues
118: */
119: public void setValidRange(final double minValue,
120: final double maxValue) {
121: final int minIndex = (int) minValue;
122: final int maxIndex = (int) maxValue;
123: if (minIndex == minValue && maxIndex == maxValue) {
124: setInteger("minValue", minIndex);
125: setInteger("maxValue", maxIndex);
126: } else {
127: setDouble("minValue", minValue);
128: setDouble("maxValue", maxValue);
129: }
130: }
131:
132: /**
133: * Returns the fill values for this band, or {@code null} if none.
134: */
135: public double[] getNoDataValues() {
136: return getDoubles("fillValues", true);
137: }
138:
139: /**
140: * Sets the fill values for this band. This method formats all fill values as integers
141: * if possible, or all values as floating points otherwise. We apply a "all or nothing"
142: * rule for consistency.
143: *
144: * @param fillValues The packed values used for missing data, or {@code null} if none.
145: *
146: * @see #setPackedValues
147: */
148: public void setNoDataValues(final double[] fillValues) {
149: if (fillValues != null) {
150: int[] asIntegers = new int[fillValues.length];
151: for (int i = 0; i < fillValues.length; i++) {
152: final double value = fillValues[i];
153: if ((asIntegers[i] = (int) value) != value) {
154: asIntegers = null; // Not integers; stop the check.
155: break;
156: }
157: }
158: if (asIntegers != null) {
159: setIntegers("fillValues", asIntegers);
160: return;
161: }
162: }
163: setDoubles("fillValues", fillValues);
164: }
165:
166: /**
167: * Defines valid and fill <em>packed</em> values as a combinaison of
168: * <code>{@linkplain #setValidRange(double,double) setValidRange}(minValue, maxValue)</code>
169: * and <code>{linkplain #setNoDataValues(double[]) setNoDataValues}(fillValues)</code>.
170: * <p>
171: * If the minimal or maximal value is {@linkplain Double#isInfinite infinite} and the data
172: * type is an integer type, then this method replaces the infinite values by default bounds
173: * inferred from the data type and the fill values.
174: *
175: * @param minValue The minimal valid <em>packed</em> value,
176: * or {@link Double#NEGATIVE_INFINITY} if unknown.
177: * @param maxValue The maximal valid <em>packed</em> value,
178: * or {@link Double#POSITIVE_INFINITY} if unknown.
179: * @param fillValues The packed values used for missing data, or {@code null} if none.
180: * @param dataType The raw data type as one of {@link DataBuffer} constants, or
181: * {@link DataBuffer#TYPE_UNDEFINED} if unknown.
182: *
183: * @see #setValidRange
184: * @see #setNoDataValues
185: */
186: public void setPackedValues(double minValue, double maxValue,
187: final double[] fillValues, final int dataType) {
188: minValue = replaceInfinity(minValue, fillValues, dataType);
189: maxValue = replaceInfinity(maxValue, fillValues, dataType);
190: setValidRange(minValue, maxValue);
191: setNoDataValues(fillValues);
192: }
193:
194: /**
195: * If the specified value is infinity, then replace that values by a bounds inferred
196: * from the specified fill values and data type.
197: *
198: * @param value The value.
199: * @param fillValues The packed values used for missing data, or {@code null} if none.
200: * @param dataType The raw data type as one of {@link DataBuffer} constants, or
201: * {@link DataBuffer#TYPE_UNDEFINED} if unknown.
202: */
203: private static double replaceInfinity(double value,
204: final double[] fillValues, final int dataType) {
205: final boolean negative;
206: if (value == Double.NEGATIVE_INFINITY) {
207: negative = true;
208: } else if (value == Double.POSITIVE_INFINITY) {
209: negative = false;
210: } else {
211: return value;
212: }
213: final double midValue;
214: switch (dataType) {
215: default: {
216: // Unsigned integer: computes the upper bound according the data length.
217: final long range = 1L << DataBuffer
218: .getDataTypeSize(dataType);
219: value = negative ? 0 : range - 1;
220: midValue = range >>> 1;
221: break;
222: }
223: case DataBuffer.TYPE_SHORT: {
224: value = negative ? Short.MIN_VALUE : Short.MAX_VALUE;
225: midValue = 0;
226: break;
227: }
228: case DataBuffer.TYPE_INT: {
229: value = negative ? Integer.MIN_VALUE : Integer.MAX_VALUE;
230: midValue = 0;
231: break;
232: }
233: case DataBuffer.TYPE_FLOAT:
234: case DataBuffer.TYPE_DOUBLE:
235: case DataBuffer.TYPE_UNDEFINED: {
236: // Unbounded or undefined type: nothing to do.
237: return value;
238: }
239: }
240: /*
241: * Considers only the fill values that are close to the bounds we just computed. We use
242: * the middle value (always 0 for signed data type) as the threshold for choosing which
243: * bounds is close to that value. In other words, for signed data type we consider only
244: * positive or negative fill values (depending the 'value' sign), not both in same time.
245: *
246: * For each fill value to consider, reduces the range of valid values in such a way that
247: * it doesn't include that fill value. The exclusion is performed by substracting 1, which
248: * should be okay since we known at this stage that the data type is integer.
249: */
250: if (fillValues != null) {
251: double valueDistance = Math.abs(value - midValue);
252: for (int i = 0; i < fillValues.length; i++) {
253: final double fillValue = fillValues[i];
254: if ((fillValue < midValue) == negative) {
255: final double fillDistance = Math.abs(fillValue
256: - midValue);
257: if (fillDistance <= valueDistance) {
258: valueDistance = fillDistance;
259: value = fillValue - 1; // Value must be exclusive.
260: }
261: }
262: }
263: }
264: return value;
265: }
266:
267: /**
268: * Returns the scale factor from packed to geophysics values, or {@code 1} if none.
269: */
270: public double getScale() {
271: final Double scale = getDouble("scale");
272: return (scale != null) ? scale.doubleValue() : 1.0;
273: }
274:
275: /**
276: * Sets the scale factor for this band.
277: *
278: * @param scale The scale from packed to geophysics values, or {@code 1} if none.
279: */
280: public void setScale(final double scale) {
281: setDouble("scale", scale);
282: }
283:
284: /**
285: * Returns the offset from packed to geophysics values, or {@code 0} if none.
286: */
287: public double getOffset() {
288: final Double offset = getDouble("offset");
289: return (offset != null) ? offset.doubleValue() : 0.0;
290: }
291:
292: /**
293: * Sets the offset for this band.
294: *
295: * @param offset The offset from packed to geophysics values, or {@code 0} if none.
296: */
297: public void setOffset(final double offset) {
298: setDouble("offset", offset);
299: }
300: }
|