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.resources.coverage;
018:
019: import java.awt.RenderingHints;
020: import java.awt.image.ColorModel;
021: import java.awt.image.IndexColorModel;
022: import java.awt.image.RenderedImage;
023: import java.util.Collection;
024: import java.util.Iterator;
025: import java.util.List;
026:
027: import javax.media.jai.Interpolation;
028: import javax.media.jai.InterpolationBilinear;
029: import javax.media.jai.InterpolationNearest;
030: import javax.media.jai.PropertySource;
031:
032: import org.opengis.coverage.SampleDimension;
033: import org.opengis.coverage.grid.GridCoverage;
034: import org.opengis.referencing.operation.MathTransform1D;
035: import org.opengis.referencing.operation.TransformException;
036:
037: import org.geotools.coverage.Category;
038: import org.geotools.coverage.GridSampleDimension;
039: import org.geotools.coverage.grid.GridCoverage2D;
040: import org.geotools.coverage.grid.RenderedCoverage;
041: import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
042: import org.geotools.factory.Hints;
043: import org.geotools.feature.FeatureCollection;
044: import org.geotools.feature.IllegalAttributeException;
045: import org.geotools.feature.SchemaException;
046: import org.geotools.util.NumberRange;
047:
048: /**
049: * A set of utilities methods for the Grid Coverage package. Those methods are not really
050: * rigorous; must of them should be seen as temporary implementations.
051: *
052: * @since 2.4
053: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/resources/coverage/CoverageUtilities.java $
054: * @version $Id: CoverageUtilities.java 25447 2007-05-06 16:56:19Z desruisseaux $
055: * @author Martin Desruisseaux
056: * @author Simone Giannecchini
057: */
058: public class CoverageUtilities {
059: /**
060: * Controlling datum shift process.
061: *
062: * @deprecated Will be deleted.
063: */
064: public final static Hints LENIENT_HINT = new Hints(
065: Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
066:
067: /**
068: * Do not allows instantiation of this class.
069: *
070: * @deprecated We will make this constructor private after we deleted
071: * the deprecated subclass, and make this class final.
072: */
073: protected CoverageUtilities() {
074: }
075:
076: /**
077: * Retrieves a best guess for the sample value to use for background,
078: * inspecting the categories of the provided {@link GridCoverage2D}.
079: *
080: * @param coverage to use for guessing background values.
081: * @return an array of double values to use as a background.
082: */
083: public static double[] getBackgroundValues(GridCoverage2D coverage) {
084: /*
085: * Get the sample value to use for background. We will try to fetch this
086: * value from one of "no data" categories. For geophysics images, it is
087: * usually NaN. For non-geophysics images, it is usually 0.
088: */
089: final GridSampleDimension[] sampleDimensions = coverage
090: .getSampleDimensions();
091: final double[] background = new double[sampleDimensions.length];
092: for (int i = 0; i < background.length; i++) {
093: final NumberRange range = sampleDimensions[i]
094: .getBackground().getRange();
095: final double min = range.getMinimum();
096: final double max = range.getMaximum();
097: if (range.isMinIncluded()) {
098: background[i] = min;
099: } else if (range.isMaxIncluded()) {
100: background[i] = max;
101: } else {
102: background[i] = 0.5 * (min + max);
103: }
104: }
105: return background;
106: }
107:
108: /**
109: * Returns {@code true} if at least one of the specified sample dimensions has a
110: * {@linkplain SampleDimension#getSampleToGeophysics sample to geophysics} transform
111: * which is not the identity transform.
112: */
113: public static boolean hasTransform(
114: final SampleDimension[] sampleDimensions) {
115: for (int i = sampleDimensions.length; --i >= 0;) {
116: SampleDimension sd = sampleDimensions[i];
117: if (sd instanceof GridSampleDimension) {
118: sd = ((GridSampleDimension) sd).geophysics(false);
119: }
120: MathTransform1D tr = sd.getSampleToGeophysics();
121: return tr != null && !tr.isIdentity();
122: }
123: return false;
124: }
125:
126: /**
127: * Returns {@code true} if the specified grid coverage or any of its source
128: * uses the following image.
129: */
130: public static boolean uses(final GridCoverage coverage,
131: final RenderedImage image) {
132: if (coverage != null) {
133: if (coverage instanceof RenderedCoverage) {
134: if (((RenderedCoverage) coverage).getRenderedImage() == image) {
135: return true;
136: }
137: }
138: final Collection sources = coverage.getSources();
139: if (sources != null) {
140: for (final Iterator it = sources.iterator(); it
141: .hasNext();) {
142: if (uses((GridCoverage) it.next(), image)) {
143: return true;
144: }
145: }
146: }
147: }
148: return false;
149: }
150:
151: /**
152: * Returns the visible band in the specified {@link RenderedImage} or {@link PropertySource}.
153: * This method fetch the {@code "GC_VisibleBand"} property. If this property is undefined,
154: * then the visible band default to the first one.
155: *
156: * @param image The image for which to fetch the visible band, or {@code null}.
157: * @return The visible band.
158: */
159: public static int getVisibleBand(final Object image) {
160: Object candidate = null;
161: if (image instanceof RenderedImage) {
162: candidate = ((RenderedImage) image)
163: .getProperty("GC_VisibleBand");
164: } else if (image instanceof PropertySource) {
165: candidate = ((PropertySource) image)
166: .getProperty("GC_VisibleBand");
167: }
168: if (candidate instanceof Integer) {
169: return ((Integer) candidate).intValue();
170: }
171: return 0;
172: }
173:
174: /**
175: * General purpose method used in various operations for {@link GridCoverage2D} to help
176: * with taking decisions on how to treat coverages with respect to their {@link ColorModel}.
177: *
178: * <p>
179: * The need for this method arose in consideration of the fact that applying most operations
180: * on coverage whose {@link ColorModel} is an instance of {@link IndexColorModel} may lead to
181: * unpredictable results depending on the applied {@link Interpolation} (think about applying
182: * "Scale" with {@link InterpolationBilinear} on a non-geophysics {@link GridCoverage2D} with an
183: * {@link IndexColorModel}) or more simply on the operation itself ("SubsampleAverage" cannot
184: * be applied at all on a {@link GridCoverage2D} backed by an {@link IndexColorModel}).
185: *
186: * <p>
187: * This method suggests the actions to take depending on the structure of the provided
188: * {@link GridCoverage2D}, the provided {@link Interpolation} and if the operation uses
189: * a filter or not (this is useful for operations like SubsampleAverage or FilteredSubsample).
190: *
191: * <p>
192: * In general the idea is as follows: If the original coverage is backed by a
193: * {@link RenderedImage} with an {@link IndexColorModel}, we have the following cases:
194: *
195: * <ul>
196: * <li>if the interpolation is {@link InterpolationNearest} and there is no filter involved
197: * we can apply the operation on the {@link IndexColorModel}-backed coverage with nor
198: * probs.</li>
199: * <li>If the interpolations in of higher order or there is a filter to apply we have to
200: * options:
201: * <ul>
202: * <li>If the coverage has a twin geophysics view we need to go back to it and apply
203: * the operation there.</li>
204: * <li>If the coverage has no geophysics view (an orthophoto with an intrisic
205: * {@link IndexColorModel} view) we need to perform an RGB(A) color expansion
206: * before applying the operation.</li>
207: * </ul>
208: * </li>
209: * </ul>
210: *
211: * <p>
212: * A special case is when we want to apply an operation on the geophysics view of a coverage that
213: * does not involve high order interpolation of filters. In this case we suggest to apply the
214: * operation on the non-geophysics view, which is usually much faster. Users may ignore this
215: * advice.
216: *
217: * @param coverage to check for the action to take.
218: * @param interpolation to use for the action to take.
219: * @param hasFilter if the operation we will apply is going to use a filter.
220: * @param hints to use when applying a certain operation.
221: * @return 0 if nothing has to be done on the provided coverage, 1 if a color expansion has to be
222: * provided, 2 if we need to employ the geophysics vew of the provided coverage,
223: * 3 if we suggest to employ the non-geophysics vew of the provided coverage.
224: *
225: * @since 2.3.1
226: *
227: * @todo Consider refactoring this method into {@link org.geotools.coverage.grid.ViewType},
228: * or in some utility class related to it.
229: */
230: public static int prepareSourcesForGCOperation(
231: final GridCoverage2D coverage,
232: final Interpolation interpolation, final boolean hasFilter,
233: final RenderingHints hints) {
234: final RenderedImage sourceImage = coverage.getRenderedImage();
235: boolean useNonGeoView = false;
236: if (hints != null) {
237: // REPLACE_NON_GEOPHYSICS_VIEW default value is 'true'.
238: // useNonGeoView value is the opposite of REPLACE_NON_GEOPHYSICS_VIEW.
239: useNonGeoView = Boolean.FALSE.equals(hints
240: .get(Hints.REPLACE_NON_GEOPHYSICS_VIEW));
241: }
242: // the color model is indexed?
243: final boolean isIndexColorModel = sourceImage.getColorModel() instanceof IndexColorModel;
244: if (!isIndexColorModel) {
245: return 0;// optimization
246: }
247: final boolean isNearestNeigborInterpolation = interpolation instanceof InterpolationNearest;
248: // /////////////////////////////////////////////////////////////////////
249: //
250: // The projection are usually applied on floating-point values, in order
251: // to gets maximal precision and to handle correctly the special case of
252: // NaN values. However, we can apply the projection on integer values if
253: // the interpolation type is "nearest neighbor", since this is not
254: // really an interpolation.
255: //
256: // If this condition is met, then we verify if an "integer version" of
257: // the image is available as a source of the source coverage (i.e. the
258: // floating-point image is derived from the integer image, not the
259: // converse).
260: //
261: // /////////////////////////////////////////////////////////////////////
262: if (isNearestNeigborInterpolation && !hasFilter) {
263: final GridCoverage2D candidate = coverage.geophysics(false);
264: if (candidate != coverage) {
265: final List sources = coverage.getRenderedImage()
266: .getSources();
267: if (sources != null) {
268: if (sources.contains(candidate.getRenderedImage())) {
269: return 3;
270: }
271: }
272: }
273: }
274:
275: // /////////////////////////////////////////////////////////////////////
276: //
277: // Do we need to explode the Palette to RGB(A)? This is needed only when
278: // we have a coverage that has a geoophysiscs view which has itself
279: // an IndexColorModel and we want to perform an operation that involves
280: // an higher order interpolation or a filter (like with
281: // SubsampleAverage).
282: //
283: // /////////////////////////////////////////////////////////////////////
284: // do we have transforms?
285: final boolean hasRenderingCategories = hasRenderingCategories(coverage);
286: final boolean preprocessIndexed = isIndexColorModel
287: && (!isNearestNeigborInterpolation || hasFilter);
288: final boolean getGeophysics = !useNonGeoView
289: && (hasRenderingCategories && preprocessIndexed);
290: // this coverage is a real image with index color model, hence we need
291: // to apply this operation on the expanded model.
292: if (preprocessIndexed) {
293: if (!getGeophysics) {
294: return 1;
295: } else if (getGeophysics) {
296: // in this case we need to go back the geophysics view of the
297: // source coverage
298: return 2;
299: }
300: }
301: return 0;
302: }
303:
304: /**
305: * Returns {@code true} if the provided {@link GridCoverage}
306: * has {@link Category} objects twith a real transformation.
307: *
308: * <p>
309: * Common use case for this method is understanding if a
310: * {@link GridCoverage} has an accompanying Gephysiscs or non-Geophysics
311: * view, which means a dicotomy between the coverage with the "real" data
312: * and the coverage with the rendered version of the original data exists.
313: * An example is when you have raw data whose data type is float and you
314: * want to render them using a palette. You usually do this by specifying a
315: * set of {@link Category} object which will map some intervals of the raw
316: * data to some specific colors. The rendered version that we will create
317: * using the method {@link GridCoverage2D#geophysics(false)} will be backed
318: * by a RenderedImage with an IndexColorModel representing the colors
319: * provided in the Categories.
320: *
321: * @param gridCoverage
322: * to check for the existence of categories with tranformations
323: * between original data and their rendered counterpart.
324: * @return {@code false} if this coverage has only a single view associated with it,
325: * {@code true} otherwise.
326: */
327: public static boolean hasRenderingCategories(
328: final GridCoverage gridCoverage) {
329: // getting all the SampleDimensions of this coverage, if any exist
330: final int numSampleDimensions = gridCoverage
331: .getNumSampleDimensions();
332: if (numSampleDimensions == 0) {
333: return false;
334: }
335: final SampleDimension[] sampleDimensions = new SampleDimension[numSampleDimensions];
336: for (int i = 0; i < numSampleDimensions; i++) {
337: sampleDimensions[i] = gridCoverage.getSampleDimension(i);
338: }
339: // do they have any transformation that is not the identity?
340: return hasTransform(sampleDimensions);
341: }
342:
343: /**
344: * Wraps a grid coverage into a Feature. Code lifted from ArcGridDataSource
345: * (temporary).
346: *
347: * @deprecated Moved to {@link FeatureUtilities#wrapGridCoverage}.
348: */
349: public static FeatureCollection wrapGc(final GridCoverage coverage)
350: throws TransformException, SchemaException,
351: IllegalAttributeException {
352: return FeatureUtilities
353: .wrapGridCoverage((GridCoverage2D) coverage);
354: }
355:
356: /**
357: * Wraps a grid coverage into a Feature. Code lifted from ArcGridDataSource
358: * (temporary).
359: *
360: * @param gridCoverageReader the grid coverage
361: * @return a feature with the grid coverage envelope as the geometry and the
362: * grid coverage itself in the "grid" attribute
363: *
364: * @deprecated Moved to {@link FeatureUtilities#wrapGridCoverageReader}.
365: */
366: public static FeatureCollection wrapGcReader(
367: AbstractGridCoverage2DReader reader)
368: throws TransformException, SchemaException,
369: IllegalAttributeException {
370: return FeatureUtilities.wrapGridCoverageReader(reader);
371: }
372: }
|