001: /*
002: * GeoTools - OpenSource mapping toolkit
003: * http://geotools.org
004: * (C) 2003-2006, Geotools Project Managment Committee (PMC)
005: * (C) 2001, Institut de Recherche pour le Développement
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; either
010: * version 2.1 of the License, or (at your option) any later version.
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.coverage.grid;
018:
019: // J2SE dependencies and extensions
020: import java.awt.Color;
021: import java.awt.RenderingHints;
022: import java.awt.image.ColorModel;
023: import java.awt.image.DataBuffer;
024: import java.awt.image.Raster;
025: import java.awt.image.RenderedImage;
026: import java.awt.image.SampleModel;
027: import java.util.Arrays;
028: import javax.units.Unit;
029:
030: // JAI dependencies
031: import javax.media.jai.iterator.RectIter;
032: import javax.media.jai.iterator.RectIterFactory;
033:
034: // OpenGIS dependencies
035: import org.opengis.coverage.ColorInterpretation;
036: import org.opengis.coverage.SampleDimensionType;
037: import org.opengis.util.InternationalString;
038:
039: // Geotools dependencies
040: import org.geotools.coverage.Category;
041: import org.geotools.coverage.GridSampleDimension;
042: import org.geotools.coverage.TypeMap;
043: import org.geotools.factory.Hints;
044: import org.geotools.referencing.operation.transform.LinearTransform1D;
045: import org.geotools.resources.ClassChanger;
046: import org.geotools.resources.i18n.Errors;
047: import org.geotools.resources.i18n.ErrorKeys;
048: import org.geotools.util.NumberRange;
049: import org.geotools.util.SimpleInternationalString;
050:
051: /**
052: * Describes the band values for a grid coverage.
053: *
054: * @since 2.1
055: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/grid/Grid2DSampleDimension.java $
056: * @version $Id: Grid2DSampleDimension.java 22817 2006-11-17 17:24:55Z desruisseaux $
057: * @author Martin Desruisseaux
058: */
059: final class Grid2DSampleDimension extends GridSampleDimension {
060: /**
061: * Serial number for interoperability with different versions.
062: */
063: private static final long serialVersionUID = 946331925096804779L;
064:
065: /**
066: * Band number for this sample dimension.
067: */
068: private final int band;
069:
070: /**
071: * The number of bands in the {@link GridCoverage} who own this sample dimension.
072: */
073: private final int numBands;
074:
075: /**
076: * The grid value data type.
077: */
078: private final SampleDimensionType type;
079:
080: /**
081: * Constructs a sample dimension with a set of categories from an other sample dimension.
082: *
083: * @param band The originating sample dimension.
084: * @param image The image to be wrapped by {@link GridCoverage}.
085: * @param bandNumber The band number.
086: */
087: private Grid2DSampleDimension(final GridSampleDimension band,
088: final RenderedImage image, final int bandNumber) {
089: super (band);
090: final SampleModel model = image.getSampleModel();
091: this .band = bandNumber;
092: this .numBands = model.getNumBands();
093: this .type = TypeMap.getSampleDimensionType(model, bandNumber);
094: }
095:
096: /**
097: * Creates a set of sample dimensions for the given image. The array length of both
098: * arguments must matches the number of bands in the supplied {@code image}.
099: *
100: * @param name The name for data (e.g. "Elevation").
101: * @param image The image for which to create a set of sample dimensions.
102: * @param src User-provided sample dimensions, or {@code null} if none.
103: * @param dst The array where to put sample dimensions.
104: * @return {@code true} if all sample dimensions are geophysics (quantitative), or
105: * {@code false} if all sample dimensions are non-geophysics (qualitative).
106: * @throws IllegalArgumentException if geophysics and non-geophysics dimensions are mixed.
107: */
108: static boolean create(final CharSequence name,
109: final RenderedImage image, final GridSampleDimension[] src,
110: final GridSampleDimension[] dst) {
111: final int numBands = image.getSampleModel().getNumBands();
112: if (src != null && src.length != numBands) {
113: throw new IllegalArgumentException(Errors.format(
114: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, new Integer(
115: numBands), new Integer(src.length),
116: "SampleDimension"));
117: }
118: if (dst.length != numBands) {
119: throw new IllegalArgumentException(Errors.format(
120: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, new Integer(
121: numBands), new Integer(dst.length),
122: "SampleDimension"));
123: }
124: /*
125: * Now, we know that the number of bands and the array length are consistent.
126: * Search if there is any null SampleDimension. If any, replace the null value
127: * by a default SampleDimension. In all cases, count the number of geophysics
128: * and non-geophysics sample dimensions.
129: */
130: int countGeophysics = 0;
131: int countIndexed = 0;
132: GridSampleDimension[] defaultSD = null;
133: for (int i = 0; i < numBands; i++) {
134: GridSampleDimension sd = (src != null) ? src[i] : null;
135: if (sd == null) {
136: /*
137: * If the user didn't provided explicitly a SampleDimension, create a default one.
138: * We will creates a SampleDimension for all bands in one step, even if only a few
139: * of them are required.
140: */
141: if (defaultSD == null) {
142: defaultSD = new GridSampleDimension[numBands];
143: create(name, RectIterFactory.create(image, null),
144: image.getSampleModel(), null, null, null,
145: null, defaultSD, null);
146: }
147: sd = defaultSD[i];
148: }
149: sd = new Grid2DSampleDimension(sd, image, i);
150: dst[i] = sd;
151: if (sd.geophysics(true) == sd)
152: countGeophysics++;
153: if (sd.geophysics(false) == sd)
154: countIndexed++;
155: }
156: if (countGeophysics == numBands) {
157: return true;
158: }
159: if (countIndexed == numBands) {
160: return false;
161: }
162: throw new IllegalArgumentException(Errors
163: .format(ErrorKeys.MIXED_CATEGORIES));
164: }
165:
166: /**
167: * Creates a set of sample dimensions for the given raster.
168: *
169: * @param name The name for data (e.g. "Elevation").
170: * @param raster The raster.
171: * @param min The minimal value for each bands, or {@code null} for computing it automatically.
172: * @param max The maximal value for each bands, or {@code null} for computing it automatically.
173: * @param units The units of sample values, or {@code null} if unknow.
174: * @param colors The colors to use for values from {@code min} to {@code max} for each
175: * bands, or {@code null} for a default color palette. If non-null, each arrays
176: * {@code colors[b]} may have any length; colors will be interpolated as needed.
177: * @param hints An optional set of rendering hints, or {@code null} if none. Those hints will
178: * not affect the sample dimensions to be created. However, they may affect the sample
179: * dimensions to be returned by <code>{@link #geophysics geophysics}(false)</code>, i.e.
180: * the view to be used at rendering time. The optional hint
181: * {@link Hints#SAMPLE_DIMENSION_TYPE} specifies the {@link SampleDimensionType}
182: * to be used at rendering time, which can be one of
183: * {@link SampleDimensionType#UBYTE UBYTE} or
184: * {@link SampleDimensionType#USHORT USHORT}.
185: * @return The sample dimension for the given raster.
186: */
187: static GridSampleDimension[] create(final CharSequence name,
188: final Raster raster, final double[] min,
189: final double[] max, final Unit units,
190: final Color[][] colors, final RenderingHints hints) {
191: final GridSampleDimension[] dst = new GridSampleDimension[raster
192: .getNumBands()];
193: create(name, (min == null || max == null) ? RectIterFactory
194: .create(raster, null) : null, raster.getSampleModel(),
195: min, max, units, colors, dst, hints);
196: return dst;
197: }
198:
199: /**
200: * Creates a set of sample dimensions for the data backing the given iterator.
201: *
202: * @param name The name for data (e.g. "Elevation").
203: * @param iterator The iterator through the raster data, or {@code null}.
204: * @param model The image or raster sample model.
205: * @param min The minimal value, or {@code null} for computing it automatically.
206: * @param max The maximal value, or {@code null} for computing it automatically.
207: * @param units The units of sample values, or {@code null} if unknow.
208: * @param colors The colors to use for values from {@code min} to {@code max} for each bands,
209: * or {@code null} for a default color palette. If non-null, each arrays
210: * {@code colors[b]} may have any length; colors will be interpolated as needed.
211: * @param dst The array where to store sample dimensions. The array length must matches
212: * the number of bands.
213: * @param hints An optional set of rendering hints, or {@code null} if none.
214: * Those hints will not affect the sample dimensions to be created. However,
215: * they may affect the sample dimensions to be returned by
216: * <code>{@link #geophysics geophysics}(false)</code>, i.e.
217: * the view to be used at rendering time. The optional hint
218: * {@link Hints#SAMPLE_DIMENSION_TYPE} specifies the {@link SampleDimensionType}
219: * to be used at rendering time, which can be one of
220: * {@link SampleDimensionType#UBYTE UBYTE} or
221: * {@link SampleDimensionType#USHORT USHORT}.
222: */
223: private static void create(final CharSequence name,
224: final RectIter iterator, final SampleModel model,
225: double[] min, double[] max, final Unit units,
226: final Color[][] colors, final GridSampleDimension[] dst,
227: final RenderingHints hints) {
228: final int numBands = dst.length;
229: if (min != null && min.length != numBands) {
230: throw new IllegalArgumentException(Errors.format(
231: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, new Integer(
232: numBands), new Integer(min.length),
233: "min[i]"));
234: }
235: if (max != null && max.length != numBands) {
236: throw new IllegalArgumentException(Errors.format(
237: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, new Integer(
238: numBands), new Integer(max.length),
239: "max[i]"));
240: }
241: if (colors != null && colors.length != numBands) {
242: throw new IllegalArgumentException(Errors.format(
243: ErrorKeys.NUMBER_OF_BANDS_MISMATCH_$3, new Integer(
244: numBands), new Integer(colors.length),
245: "colors[i]"));
246: }
247: /*
248: * Arguments are know to be valids. We now need to compute two ranges:
249: *
250: * STEP 1: Range of target (sample) values. This is computed in the following block.
251: * STEP 2: Range of source (geophysics) values. It will be computed one block later.
252: *
253: * The target (sample) values will typically range from 0 to 255 or 0 to 65535, but the
254: * general case is handled as well. If the source (geophysics) raster uses floating point
255: * numbers, then a "nodata" category may be added in order to handle NaN values. If the
256: * source raster use integer numbers instead, then we will rescale samples only if they
257: * would not fit in the target data type.
258: */
259: final SampleDimensionType sourceType = TypeMap
260: .getSampleDimensionType(model, 0);
261: final boolean sourceIsFloat = TypeMap
262: .isFloatingPoint(sourceType);
263: SampleDimensionType targetType = null;
264: if (hints != null) {
265: targetType = (SampleDimensionType) hints
266: .get(Hints.SAMPLE_DIMENSION_TYPE);
267: }
268: if (targetType == null) {
269: // Default to TYPE_BYTE for floating point images only; otherwise keep unchanged.
270: targetType = sourceIsFloat ? SampleDimensionType.UNSIGNED_8BITS
271: : sourceType;
272: }
273: // Default setting: no scaling
274: final boolean targetIsFloat = TypeMap
275: .isFloatingPoint(targetType);
276: NumberRange targetRange = TypeMap.getRange(targetType);
277: Category[] categories = new Category[1];
278: final boolean needScaling;
279: if (targetIsFloat) {
280: // Never rescale if the target is floating point numbers.
281: needScaling = false;
282: } else if (sourceIsFloat) {
283: // Always rescale for "float to integer" conversions. In addition,
284: // Use 0 value as a "no data" category for unsigned data type only.
285: needScaling = true;
286: if (!TypeMap.isSigned(targetType)) {
287: categories = new Category[2];
288: categories[1] = Category.NODATA;
289: targetRange = TypeMap.getPositiveRange(targetType);
290: }
291: } else {
292: // In "integer to integer" conversions, rescale only if
293: // the target range is smaller than the source range.
294: needScaling = !targetRange.contains(TypeMap
295: .getRange(sourceType));
296: }
297: /*
298: * Computes the minimal and maximal values, if not explicitely provided.
299: * This information is required for determining the range of geophysics
300: * values.
301: */
302: if (needScaling && (min == null || max == null)) {
303: final boolean computeMin;
304: final boolean computeMax;
305: if (computeMin = (min == null)) {
306: min = new double[numBands];
307: Arrays.fill(min, Double.POSITIVE_INFINITY);
308: }
309: if (computeMax = (max == null)) {
310: max = new double[numBands];
311: Arrays.fill(max, Double.NEGATIVE_INFINITY);
312: }
313: int b = 0;
314: iterator.startBands();
315: if (!iterator.finishedBands())
316: do {
317: iterator.startLines();
318: if (!iterator.finishedLines())
319: do {
320: iterator.startPixels();
321: if (!iterator.finishedPixels())
322: do {
323: final double z = iterator
324: .getSampleDouble();
325: if (computeMin && z < min[b])
326: min[b] = z;
327: if (computeMax && z > max[b])
328: max[b] = z;
329: } while (!iterator.nextPixelDone());
330: } while (!iterator.nextLineDone());
331: if (computeMin && computeMax) {
332: if (!(min[b] < max[b])) {
333: min[b] = 0;
334: max[b] = 1;
335: }
336: }
337: b++;
338: } while (!iterator.nextBandDone());
339: }
340: /*
341: * Now, constructs the sample dimensions. We will inconditionnaly provides a "nodata"
342: * category for floating point images targeting unsigned integers, since we don't know
343: * if the user plan to have NaN values. Even if the current image doesn't have NaN values,
344: * it could have NaN later if the image uses a writable raster.
345: */
346: final InternationalString n = SimpleInternationalString
347: .wrap(name);
348: NumberRange sourceRange = TypeMap.getRange(sourceType);
349: for (int b = 0; b < numBands; b++) {
350: final Color[] c = colors != null ? colors[b] : null;
351: if (needScaling) {
352: sourceRange = new NumberRange(min[b], max[b])
353: .castTo(sourceRange.getElementClass());
354: categories[0] = new Category(n, c, targetRange,
355: sourceRange);
356: } else {
357: categories[0] = new Category(n, c, targetRange,
358: LinearTransform1D.IDENTITY);
359: }
360: dst[b] = new GridSampleDimension(categories, units)
361: .geophysics(true);
362: }
363: }
364:
365: /**
366: * Returns a code value indicating grid value data type.
367: * This will also indicate the number of bits for the data type.
368: *
369: * @return a code value indicating grid value data type.
370: */
371: public SampleDimensionType getSampleDimensionType() {
372: return type;
373: }
374:
375: /**
376: * Returns the color interpretation of the sample dimension.
377: */
378: public ColorInterpretation getColorInterpretation() {
379: return TypeMap.getColorInterpretation(getColorModel(), band);
380: }
381:
382: /**
383: * Returns a color model for this sample dimension.
384: */
385: public ColorModel getColorModel() {
386: return getColorModel(band, numBands);
387: }
388:
389: /**
390: * Returns the minimum value occurring in this sample dimension.
391: */
392: // public double getMinimumValue()
393: // {return getHistogram().getLowValue(band);}
394: /**
395: * Returns the maximum value occurring in this sample dimension.
396: */
397: // public double getMaximumValue()
398: // {return getHistogram().getHighValue(band);}
399: /**
400: * Determine the mode grid value in this sample dimension.
401: */
402: // public double getModeValue()
403: // {throw new UnsupportedOperationException("Not implemented");}
404: /**
405: * Determine the median grid value in this sample dimension.
406: */
407: // public double getMedianValue()
408: // {throw new UnsupportedOperationException("Not implemented");}
409: /**
410: * Determine the mean grid value in this sample dimension.
411: */
412: // public double getMeanValue()
413: // {return getHistogram().getMean()[band];}
414: /**
415: * Determine the standard deviation from the mean
416: * of the grid values in a sample dimension.
417: */
418: // public double getStandardDeviation()
419: // {return getHistogram().getStandardDeviation()[band];}
420: /**
421: * Gets the histogram for the underlying grid coverage.
422: */
423: // private Histogram getHistogram()
424: // {throw new UnsupportedOperationException("Not implemented");}
425: }
|