0001: /*
0002: * GeoTools - OpenSource mapping toolkit
0003: * http://geotools.org
0004: * (C) 2005-2006, Geotools Project Managment Committee (PMC)
0005: * (C) 2001, Institut de Recherche pour le Développement
0006: *
0007: * This library is free software; you can redistribute it and/or
0008: * modify it under the terms of the GNU Lesser General Public
0009: * License as published by the Free Software Foundation; either
0010: * version 2.1 of the License, or (at your option) any later version.
0011: *
0012: * This library is distributed in the hope that it will be useful,
0013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
0015: * Lesser General Public License for more details.
0016: */
0017: package org.geotools.coverage.processing;
0018:
0019: // J2SE dependencies and extensions
0020: import java.awt.RenderingHints;
0021: import java.awt.image.ColorModel;
0022: import java.awt.image.RenderedImage;
0023: import java.io.Serializable;
0024: import java.util.Map;
0025: import java.util.Arrays;
0026: import java.util.Locale;
0027: import java.util.Collections;
0028: import javax.units.Unit;
0029:
0030: // JAI dependencies
0031: import javax.media.jai.ImageLayout;
0032: import javax.media.jai.JAI;
0033: import javax.media.jai.OperationRegistry;
0034: import javax.media.jai.OperationDescriptor;
0035: import javax.media.jai.ParameterBlockJAI;
0036: import javax.media.jai.registry.RenderedRegistryMode;
0037:
0038: // OpenGIS dependencies
0039: import org.opengis.coverage.Coverage;
0040: import org.opengis.coverage.processing.OperationNotFoundException;
0041: import org.opengis.referencing.FactoryException;
0042: import org.opengis.referencing.IdentifiedObject;
0043: import org.opengis.referencing.crs.CoordinateReferenceSystem;
0044: import org.opengis.referencing.operation.MathTransform;
0045: import org.opengis.referencing.operation.MathTransform2D;
0046: import org.opengis.referencing.operation.MathTransformFactory;
0047: import org.opengis.referencing.operation.TransformException;
0048: import org.opengis.parameter.ParameterDescriptorGroup;
0049: import org.opengis.parameter.ParameterNotFoundException;
0050: import org.opengis.parameter.ParameterValueGroup;
0051: import org.opengis.util.InternationalString;
0052:
0053: // Geotools dependencies
0054: import org.geotools.coverage.Category;
0055: import org.geotools.coverage.GridSampleDimension;
0056: import org.geotools.coverage.grid.ViewType;
0057: import org.geotools.coverage.grid.GridCoverage2D;
0058: import org.geotools.coverage.grid.GridGeometry2D;
0059: import org.geotools.coverage.grid.InvalidGridGeometryException;
0060: import org.geotools.factory.Hints;
0061: import org.geotools.parameter.ImagingParameters;
0062: import org.geotools.parameter.ImagingParameterDescriptors;
0063: import org.geotools.referencing.CRS;
0064: import org.geotools.referencing.ReferencingFactoryFinder;
0065: import org.geotools.referencing.operation.transform.DimensionFilter;
0066: import org.geotools.image.jai.Registry;
0067: import org.geotools.resources.XArray;
0068: import org.geotools.resources.Utilities;
0069: import org.geotools.resources.CRSUtilities;
0070: import org.geotools.resources.coverage.CoverageUtilities;
0071: import org.geotools.resources.i18n.Errors;
0072: import org.geotools.resources.i18n.ErrorKeys;
0073: import org.geotools.resources.image.ImageUtilities;
0074: import org.geotools.util.AbstractInternationalString;
0075: import org.geotools.util.NumberRange;
0076: import org.geotools.util.logging.Logging;
0077:
0078: /**
0079: * Wraps a JAI's {@link OperationDescriptor} for interoperability with
0080: * <A HREF="http://java.sun.com/products/java-media/jai/">Java Advanced Imaging</A>.
0081: * This class help to leverage the rich set of JAI operators in an GeoAPI framework.
0082: * {@code OperationJAI} inherits operation name and argument types from {@link OperationDescriptor},
0083: * except the source argument type (usually <code>{@linkplain RenderedImage}.class</code>) which is
0084: * set to <code>{@linkplain GridCoverage2D}.class</code>. If there is only one source argument, it
0085: * will be renamed {@code "source"} for better compliance with OpenGIS usage.
0086: * <p>
0087: * The entry point for applying an operation is the usual {@link #doOperation doOperation} method.
0088: * The default implementation forward the call to other methods for different bits of tasks,
0089: * resulting in the following chain of calls:
0090: * <p>
0091: * <blockquote><table>
0092: * <tr><td>{@link #doOperation doOperation}: </td>
0093: * <td>the entry point.</td></tr>
0094: * <tr><td>{@link #resampleToCommonGeometry resampleToCommonGeometry}: </td>
0095: * <td>reprojects all sources to the same coordinate reference system.</td></tr>
0096: * <tr><td>{@link #deriveGridCoverage deriveGridCoverage}: </td>
0097: * <td>gets the destination properties.</td></tr>
0098: * <tr><td>{@link #deriveSampleDimension deriveSampleDimension}: </td>
0099: * <td>gets the destination sample dimensions.</td></tr>
0100: * <tr><td>{@link #deriveCategory deriveCategory}: </td>
0101: * <td>gets the destination categories.</td></tr>
0102: * <tr><td>{@link #deriveRange deriveRange}: </td>
0103: * <td>gets the expected range of values.</td></tr>
0104: * <tr><td>{@link #deriveUnit deriveUnit}: </td>
0105: * <td>gets the destination units.</td></tr>
0106: * <tr><td>{@link #createRenderedImage createRenderedImage}: </td>
0107: * <td>the actual call to {@link JAI#createNS JAI.createNS}.</td></tr>
0108: * </table></blockquote>
0109: *
0110: * @since 2.2
0111: * @source $URL: http://svn.geotools.org/geotools/tags/2.4.1/modules/library/coverage/src/main/java/org/geotools/coverage/processing/OperationJAI.java $
0112: * @version $Id: OperationJAI.java 27848 2007-11-12 13:10:32Z desruisseaux $
0113: * @author Martin Desruisseaux
0114: * @author Simone Giannecchini
0115: */
0116: public class OperationJAI extends Operation2D {
0117: /**
0118: * Serial number for interoperability with different versions.
0119: */
0120: private static final long serialVersionUID = -5974520239347639965L;
0121:
0122: /**
0123: * The rendered mode for JAI operation.
0124: */
0125: protected static final String RENDERED_MODE = RenderedRegistryMode.MODE_NAME;
0126:
0127: /**
0128: * The JAI's operation descriptor.
0129: */
0130: protected final OperationDescriptor operation;
0131:
0132: /**
0133: * Constructs a grid coverage operation from a JAI operation name. This convenience
0134: * constructor fetch the {@link OperationDescriptor} from the specified operation
0135: * name using the default {@link JAI} instance.
0136: *
0137: * @param operation JAI operation name (e.g. {@code "GradientMagnitude"}).
0138: * @throws OperationNotFoundException if no JAI descriptor was found for the given name.
0139: */
0140: public OperationJAI(final String operation)
0141: throws OperationNotFoundException {
0142: this (getOperationDescriptor(operation));
0143: }
0144:
0145: /**
0146: * Constructs a grid coverage operation backed by a JAI operation. The operation descriptor
0147: * must supports the {@code "rendered"} mode (which is the case for most JAI operations).
0148: *
0149: * @param operation The JAI operation descriptor.
0150: */
0151: public OperationJAI(final OperationDescriptor operation) {
0152: this (operation, new ImagingParameterDescriptors(operation));
0153: }
0154:
0155: /**
0156: * Constructs a grid coverage operation backed by a JAI operation. The operation descriptor
0157: * must supports the {@code "rendered"} mode (which is the case for most JAI operations).
0158: *
0159: * @param operation The JAI operation descriptor.
0160: * @param descriptor The OGC parameters descriptor.
0161: */
0162: protected OperationJAI(final OperationDescriptor operation,
0163: final ParameterDescriptorGroup descriptor) {
0164: super (descriptor);
0165: this .operation = operation;
0166: ensureNonNull("operation", operation);
0167: /*
0168: * Check argument validity.
0169: */
0170: ensureRenderedImage(operation.getDestClass(RENDERED_MODE));
0171: final Class[] sourceClasses = operation
0172: .getSourceClasses(RENDERED_MODE);
0173: if (sourceClasses != null) {
0174: final int length = sourceClasses.length;
0175: assert length == operation.getNumSources();
0176: for (int i = 0; i < length; i++) {
0177: ensureRenderedImage(sourceClasses[i]);
0178: }
0179: }
0180: assert super .getNumSources() == operation.getNumSources();
0181: }
0182:
0183: /**
0184: * Returns the operation descriptor for the specified JAI operation name. This method
0185: * uses the default {@link JAI} instance and looks for the {@value #RENDERED_MODE} mode.
0186: *
0187: * @param name The operation name.
0188: * @return The operation descriptor for the given name.
0189: * @throws OperationNotFoundException if no JAI descriptor was found for the given name.
0190: *
0191: * @since 2.4
0192: */
0193: protected static OperationDescriptor getOperationDescriptor(
0194: final String name) throws OperationNotFoundException {
0195: final OperationRegistry registry = JAI.getDefaultInstance()
0196: .getOperationRegistry();
0197: OperationDescriptor operation = (OperationDescriptor) registry
0198: .getDescriptor(RENDERED_MODE, name);
0199: if (operation != null) {
0200: return operation;
0201: }
0202: if (name.startsWith("org.geotools.")
0203: && registry.getDescriptor(RENDERED_MODE,
0204: "org.geotools.Combine") == null) {
0205: try {
0206: // try and register our operations
0207: Registry.registerGeotoolsServices(registry);
0208: } catch (RuntimeException e) {
0209: Logging.GEOTOOLS.unexpectedException(
0210: AbstractProcessor.LOGGER, OperationJAI.class,
0211: "getOperationDescriptor", e);
0212: }
0213:
0214: // try to get it again
0215: operation = (OperationDescriptor) registry.getDescriptor(
0216: RENDERED_MODE, name);
0217: if (operation != null) {
0218: return operation;
0219: }
0220: }
0221: throw new OperationNotFoundException(Errors.format(
0222: ErrorKeys.OPERATION_NOT_FOUND_$1, name));
0223: }
0224:
0225: /**
0226: * Ensures that the specified class is assignable to {@link RenderedImage}.
0227: */
0228: private static final void ensureRenderedImage(final Class classe)
0229: throws IllegalArgumentException {
0230: if (!RenderedImage.class.isAssignableFrom(classe)) {
0231: // TODO: provide localized message
0232: throw new IllegalArgumentException(classe.getName());
0233: }
0234: }
0235:
0236: /**
0237: * Copies parameter values from the specified {@link ParameterValueGroup} to the
0238: * {@link ParameterBlockJAI}, except the sources.
0239: * <p>
0240: * <b>Note:</b> it would be possible to use {@link ImagingParameters#parameters}
0241: * directly in some occasions. However, we peform an unconditional copy instead
0242: * because some operations (e.g. "GradientMagnitude") may change the values.
0243: *
0244: * @param parameters The {@link ParameterValueGroup} to be copied.
0245: * @return A copy of the provided {@link ParameterValueGroup} as a JAI block.
0246: *
0247: * @since 2.4
0248: */
0249: protected ParameterBlockJAI prepareParameters(
0250: final ParameterValueGroup parameters) {
0251: final ImagingParameters copy = (ImagingParameters) descriptor
0252: .createValue();
0253: final ParameterBlockJAI block = (ParameterBlockJAI) copy.parameters;
0254: org.geotools.parameter.Parameters.copy(parameters, copy);
0255: return block;
0256: }
0257:
0258: /**
0259: * Applies a process operation to a grid coverage.
0260: * The default implementation performs the following steps:
0261: *
0262: * <ol>
0263: * <li>Converts source grid coverages to their <cite>geophysics</cite> view using
0264: * <code>{@linkplain GridCoverage2D#geophysics GridCoverage2D.geophysics}(true)</code>.
0265: * This allow to performs all computation on geophysics values instead of encoded
0266: * samples. <strong>Note:</strong> this step is disabled if
0267: * {@link #computeOnGeophysicsValues computeOnGeophysicsValues} returns
0268: * {@code false}.</li>
0269: *
0270: * <li>Ensures that every sources {@code GridCoverage2D}s use the same coordinate reference
0271: * system (at least for the two-dimensional part) with the same
0272: * {@link GridGeometry2D#getGridToCRS2D gridToCRS} relationship.</li>
0273: *
0274: * <li>Invokes {@link #deriveGridCoverage}.
0275: * The sources in the {@code ParameterBlock} are {@link RenderedImage} objects
0276: * obtained from {@link GridCoverage2D#getRenderedImage()}.</li>
0277: *
0278: * <li>If a changes from non-geophysics to geophysics view were performed at step 1,
0279: * converts the result back to the original view using
0280: * <code>{@linkplain GridCoverage2D#geophysics GridCoverage2D.geophysics}(false)</code>.
0281: * </li>
0282: * </ol>
0283: *
0284: * @param parameters List of name value pairs for the parameters required for the operation.
0285: * @param hints A set of rendering hints, or {@code null} if none.
0286: * @return The result as a grid coverage.
0287: * @throws CoverageProcessingException if the operation can't be applied.
0288: *
0289: * @see #deriveGridCoverage
0290: */
0291: public Coverage doOperation(final ParameterValueGroup parameters,
0292: final Hints hints) throws CoverageProcessingException {
0293: final ParameterBlockJAI block = prepareParameters(parameters);
0294: /*
0295: * Extracts the source grid coverages now as an array. The sources will be set in the
0296: * ParameterBlockJAI (as RenderedImages) later, after the reprojection performed in the
0297: * next block.
0298: */
0299: final String[] sourceNames = operation.getSourceNames();
0300: final GridCoverage2D[] sources = new GridCoverage2D[sourceNames.length];
0301: ViewType primarySourceType = extractSources(parameters,
0302: sourceNames, sources);
0303: /*
0304: * Ensures that all coverages use the same CRS and has the same 'gridToCRS' relationship.
0305: * After the reprojection, the method still checks all CRS in case the user overridden the
0306: * {@link #resampleToCommonGeometry} method.
0307: */
0308: resampleToCommonGeometry(sources, null, null, hints);
0309: GridCoverage2D coverage = sources[PRIMARY_SOURCE_INDEX];
0310: final CoordinateReferenceSystem crs = coverage
0311: .getCoordinateReferenceSystem2D();
0312: // TODO: remove the cast when we will be allowed to compile for J2SE 1.5.
0313: final MathTransform2D gridToCRS = ((GridGeometry2D) coverage
0314: .getGridGeometry()).getGridToCRS2D();
0315: for (int i = 0; i < sources.length; i++) {
0316: final GridCoverage2D source = sources[i];
0317: if (!CRS.equalsIgnoreMetadata(crs, source
0318: .getCoordinateReferenceSystem2D())
0319: || !CRS.equalsIgnoreMetadata(gridToCRS,
0320: ((GridGeometry2D) source.getGridGeometry())
0321: .getGridToCRS2D())) {
0322: throw new IllegalArgumentException(Errors
0323: .format(ErrorKeys.INCOMPATIBLE_GRID_GEOMETRY));
0324: }
0325: block.setSource(sourceNames[i], source.getRenderedImage());
0326: }
0327: /*
0328: * Applies the operation. This delegates the work to the chain of 'deriveXXX' methods.
0329: */
0330: coverage = deriveGridCoverage(sources, new Parameters(crs,
0331: gridToCRS, block, hints));
0332: return postProcessResult(coverage, primarySourceType);
0333: }
0334:
0335: /**
0336: * Post processing on the coverage resulting from JAI operation.
0337: *
0338: * @param coverage
0339: * {@link GridCoverage2D} resulting from the operation.
0340: * @param primarySourceType
0341: * Tells if we have to change the "geo-view" for the provided {@link GridCoverage2D}.
0342: *
0343: * @return the prepared {@link GridCoverage2D}.
0344: */
0345: private static GridCoverage2D postProcessResult(
0346: GridCoverage2D coverage, final ViewType primarySourceType) {
0347: if (primarySourceType != null) {
0348: coverage = coverage.geophysics(ViewType.GEOPHYSICS
0349: .equals(primarySourceType));
0350: }
0351: return coverage;
0352: }
0353:
0354: /**
0355: * Returns a sub-coordinate reference system for the specified dimension range.
0356: * This method is for internal use by {@link #resampleToCommonGeometry}.
0357: *
0358: * @param crs The coordinate reference system to decompose.
0359: * @param lower The first dimension to keep, inclusive.
0360: * @param upper The last dimension to keep, exclusive.
0361: * @return The sub-coordinate system, or {@code null} if {@code lower} is equals to {@code upper}.
0362: * @throws InvalidGridGeometryException if the CRS can't be separated.
0363: */
0364: private static CoordinateReferenceSystem getSubCRS(
0365: final CoordinateReferenceSystem crs, final int lower,
0366: final int upper) throws InvalidGridGeometryException {
0367: if (lower == upper) {
0368: return null;
0369: }
0370: final CoordinateReferenceSystem candidate = CRSUtilities
0371: .getSubCRS(crs, lower, upper);
0372: if (candidate == null) {
0373: throw new InvalidGridGeometryException("Unsupported CRS: "
0374: + crs.getName().getCode());
0375: }
0376: return candidate;
0377: }
0378:
0379: /**
0380: * Ensures that the source and target dimensions are the same. This method is for internal
0381: * use by {@link #resampleToCommonGeometry}.
0382: */
0383: private static void ensureStableDimensions(
0384: final DimensionFilter filter)
0385: throws InvalidGridGeometryException {
0386: final int[] source = filter.getSourceDimensions();
0387: Arrays.sort(source);
0388: final int[] target = filter.getTargetDimensions();
0389: Arrays.sort(target);
0390: if (!Arrays.equals(source, target)) {
0391: // TODO: localize
0392: throw new InvalidGridGeometryException(
0393: "Unsupported math transform.");
0394: }
0395: }
0396:
0397: /**
0398: * Resamples all sources grid coverages to the same {@linkplain GridGeometry2D two-dimensional
0399: * geometry} before to apply the {@linkplain #operation}. This method is invoked automatically
0400: * by the {@link #doOperation doOperation} method. Only the two-dimensional part is reprojected
0401: * (usually the spatial component of a CRS). Extra dimension (if any) are left unchanged. Extra
0402: * dimensions are typically time axis or depth. Note that extra dimensions are
0403: * <strong>not</strong> forced to a common geometry; only the two dimensions that apply to a
0404: * {@link javax.media.jai.PlanarImage} are. This is because the extra dimensions don't need to
0405: * be compatible for all operations. For example if a source image is a slice in a time series,
0406: * a second source image could be a slice in the frequency representation of this time series.
0407: * <p>
0408: * Subclasses should override this method if they want to specify target
0409: * {@linkplain GridGeometry2D grid geometry} and
0410: * {@linkplain CoordinateReferenceSystem coordinate reference system} different than the
0411: * default ones. For example if a subclass wants to force all images to be referenced in a
0412: * {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS#WGS84 WGS 84} CRS, then
0413: * it may overrides this method as below:
0414: *
0415: * <blockquote><pre>
0416: * protected void resampleToCommonGeometry(...) {
0417: * crs2D = DefaultGeographicCRS.WGS84;
0418: * super.resampleToCommonGeometry(sources, crs2D, gridToCrs2D, hints);
0419: * }</pre></blockquote>
0420: *
0421: * @param sources The source grid coverages to resample. This array is updated in-place as
0422: * needed (for example if a grid coverage is replaced by a projected one).
0423: * @param crs2D The target coordinate reference system to use, or {@code null} for a
0424: * default one.
0425: * @param gridToCrs2D The target "grid to coordinate reference system" transform, or
0426: * {@code null} for a default one.
0427: * @param hints The rendering hints, or {@code null} if none.
0428: *
0429: * @throws InvalidGridGeometryException if a source coverage has an unsupported grid geometry.
0430: * @throws CannotReprojectException if a grid coverage can't be resampled for some other reason.
0431: */
0432: protected void resampleToCommonGeometry(
0433: final GridCoverage2D[] sources,
0434: CoordinateReferenceSystem crs2D,
0435: MathTransform2D gridToCrs2D, final Hints hints)
0436: throws InvalidGridGeometryException,
0437: CannotReprojectException {
0438: if (sources == null || sources.length == 0) {
0439: return; // Nothing to reproject.
0440: }
0441: /*
0442: * Ensures that the target CRS is two-dimensional. If no target CRS were specified,
0443: * uses the CRS of the primary source. The math transform must be 2D too, but this
0444: * is ensured by the interface type (MathTransform2D).
0445: */
0446: final GridCoverage2D primarySource = sources[PRIMARY_SOURCE_INDEX];
0447: if (crs2D == null) {
0448: if (gridToCrs2D == null && sources.length == 1) {
0449: return; // No need to reproject.
0450: }
0451: crs2D = primarySource.getCoordinateReferenceSystem2D();
0452: } else
0453: try {
0454: crs2D = CRSUtilities.getCRS2D(crs2D);
0455: } catch (TransformException exception) {
0456: // TODO: localize
0457: throw new CannotReprojectException("Unsupported CRS: "
0458: + crs2D.getName().getCode());
0459: }
0460: if (gridToCrs2D == null) {
0461: // TODO: Remove cast when we will be allowed to compile for J2SE 1.5.
0462: gridToCrs2D = ((GridGeometry2D) primarySource
0463: .getGridGeometry()).getGridToCRS2D();
0464: }
0465: /*
0466: * 'crs2D' is the two dimensional part of the target CRS. Now for each source coverages,
0467: * substitute their two-dimensional CRS by this 'crs2D'. A source may have more than two
0468: * dimensions. For example it may have a time or a depth axis. In such case, their "head"
0469: * and "tail" CRS will be preserved before and after 'crs2D'.
0470: */
0471: final AbstractProcessor processor = getProcessor(hints);
0472: for (int i = 0; i < sources.length; i++) {
0473: final GridCoverage2D source = sources[i];
0474: final GridGeometry2D geometry = (GridGeometry2D) source
0475: .getGridGeometry();
0476: final CoordinateReferenceSystem srcCrs2D = source
0477: .getCoordinateReferenceSystem2D();
0478: final CoordinateReferenceSystem sourceCRS = source
0479: .getCoordinateReferenceSystem();
0480: final CoordinateReferenceSystem targetCRS;
0481: if (CRS.equalsIgnoreMetadata(crs2D, srcCrs2D)) {
0482: targetCRS = sourceCRS; // No reprojection needed for this source coverage.
0483: } else {
0484: /*
0485: * Replaces the 2D part in the source CRS, while preserving the leading and
0486: * trailing CRS (if any). Leading and trailing CRS are typically time axis or
0487: * depth axis. Current implementation requires that the 2D part appears in two
0488: * consecutive dimensions. Those dimensions are (0,1) in the majority of cases.
0489: */
0490: final int lowerDim = Math.min(geometry.axisDimensionX,
0491: geometry.axisDimensionY);
0492: final int upperDim = Math.max(geometry.axisDimensionX,
0493: geometry.axisDimensionY) + 1;
0494: final int sourceDim = sourceCRS.getCoordinateSystem()
0495: .getDimension();
0496: if (upperDim - lowerDim != srcCrs2D
0497: .getCoordinateSystem().getDimension()) {
0498: // TODO: localize
0499: throw new InvalidGridGeometryException(
0500: "Unsupported CRS: "
0501: + sourceCRS.getName().getCode());
0502: }
0503: final CoordinateReferenceSystem headCRS = getSubCRS(
0504: sourceCRS, 0, lowerDim);
0505: final CoordinateReferenceSystem tailCRS = getSubCRS(
0506: sourceCRS, upperDim, sourceDim);
0507: CoordinateReferenceSystem[] components = new CoordinateReferenceSystem[3];
0508: int count = 0;
0509: if (headCRS != null)
0510: components[count++] = headCRS;
0511: components[count++] = crs2D;
0512: if (tailCRS != null)
0513: components[count++] = tailCRS;
0514: components = (CoordinateReferenceSystem[]) XArray
0515: .resize(components, count);
0516: if (count == 1) {
0517: targetCRS = components[0];
0518: } else
0519: try {
0520: targetCRS = ReferencingFactoryFinder
0521: .getCRSFactory(hints)
0522: .createCompoundCRS(
0523: Collections
0524: .singletonMap(
0525: IdentifiedObject.NAME_KEY,
0526: crs2D
0527: .getName()
0528: .getCode()),
0529: components);
0530: } catch (FactoryException exception) {
0531: throw new CannotReprojectException(exception
0532: .getLocalizedMessage(), exception);
0533: }
0534: }
0535: /*
0536: * Constructs the 'gridToCRS' transform in the same way than the CRS:
0537: * leading and trailing dimensions (if any) are preserved.
0538: */
0539: final MathTransform toSource2D = geometry.getGridToCRS2D();
0540: final MathTransform toSource = geometry.getGridToCRS();
0541: MathTransform toTarget;
0542: if (CRS.equalsIgnoreMetadata(gridToCrs2D, toSource2D)) {
0543: toTarget = toSource;
0544: } else {
0545: /*
0546: * Replaces the 2D part in the source MT, while preserving the leading and
0547: * trailing MT (if any). This is similar to the 'lowerDim' and 'upperDim'
0548: * variables in the CRS case above, except that we operate on "grid" space
0549: * rather than "axis" spaces. The index are usually the same, but not always.
0550: */
0551: final int lowerDim = Math.min(geometry.gridDimensionX,
0552: geometry.gridDimensionY);
0553: final int upperDim = Math.max(geometry.gridDimensionX,
0554: geometry.gridDimensionY) + 1;
0555: final int sourceDim = toSource.getSourceDimensions();
0556: if (upperDim - lowerDim != toSource2D
0557: .getSourceDimensions()) {
0558: // TODO: localize
0559: throw new InvalidGridGeometryException(
0560: "Unsupported math transform.");
0561: }
0562: final MathTransformFactory factory = ReferencingFactoryFinder
0563: .getMathTransformFactory(hints);
0564: final DimensionFilter filter = new DimensionFilter(
0565: factory);
0566: toTarget = gridToCrs2D;
0567: try {
0568: if (lowerDim != 0) {
0569: filter.addSourceDimensionRange(0, lowerDim);
0570: MathTransform step = filter.separate(toSource);
0571: ensureStableDimensions(filter);
0572: step = factory.createPassThroughTransform(0,
0573: step, sourceDim - lowerDim);
0574: toTarget = factory.createConcatenatedTransform(
0575: step, toTarget);
0576: }
0577: if (upperDim != sourceDim) {
0578: filter.clear();
0579: filter.addSourceDimensionRange(upperDim,
0580: sourceDim);
0581: MathTransform step = filter.separate(toSource);
0582: ensureStableDimensions(filter);
0583: step = factory.createPassThroughTransform(
0584: upperDim, step, 0);
0585: toTarget = factory.createConcatenatedTransform(
0586: toTarget, step);
0587: }
0588: } catch (FactoryException exception) {
0589: throw new CannotReprojectException(Errors.format(
0590: ErrorKeys.CANT_REPROJECT_$1, source
0591: .getName()), exception);
0592: }
0593: }
0594: final GridGeometry2D targetGeom = new GridGeometry2D(null,
0595: toTarget, targetCRS);
0596: final ParameterValueGroup param = processor.getOperation(
0597: "Resample").getParameters();
0598: param.parameter("Source").setValue(source);
0599: param.parameter("GridGeometry").setValue(targetGeom);
0600: param.parameter("CoordinateReferenceSystem").setValue(
0601: targetCRS);
0602: sources[i] = (GridCoverage2D) processor.doOperation(param);
0603: }
0604: }
0605:
0606: /**
0607: * Applies a JAI operation to a grid coverage. This method is invoked automatically by
0608: * {@link #doOperation}. The default implementation performs the following steps:
0609: *
0610: * <ul>
0611: * <li>Gets the {@linkplain GridSampleDimension sample dimensions} for the target images by
0612: * invoking the {@link #deriveSampleDimension deriveSampleDimension(...)} method.</li>
0613: * <li>Applied the JAI operation using {@link #createRenderedImage}.</li>
0614: * <li>Wraps the result in a {@link GridCoverage2D} object.</li>
0615: * </ul>
0616: *
0617: * @param sources The source coverages.
0618: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0619: * @return The result as a grid coverage.
0620: *
0621: * @see #doOperation
0622: * @see #deriveSampleDimension
0623: * @see JAI#createNS
0624: */
0625: protected GridCoverage2D deriveGridCoverage(
0626: final GridCoverage2D[] sources, final Parameters parameters) {
0627: GridCoverage2D primarySource = sources[PRIMARY_SOURCE_INDEX];
0628: /*
0629: * Gets the target SampleDimensions. If they are identical to the SampleDimensions of
0630: * one of the source GridCoverage2D, then this GridCoverage2D will be used at the primary
0631: * source. It will affect the target GridCoverage2D's name and the visible band. Then,
0632: * a new color model will be constructed from the new SampleDimensions, taking in
0633: * account the visible band.
0634: */
0635: final GridSampleDimension[][] list = new GridSampleDimension[sources.length][];
0636: for (int i = 0; i < list.length; i++) {
0637: list[i] = sources[i].getSampleDimensions();
0638: }
0639: final GridSampleDimension[] sampleDims = deriveSampleDimension(
0640: list, parameters);
0641: int primarySourceIndex = -1;
0642: for (int i = 0; i < list.length; i++) {
0643: if (Arrays.equals(sampleDims, list[i])) {
0644: primarySource = sources[i];
0645: primarySourceIndex = i;
0646: break;
0647: }
0648: }
0649: /*
0650: * Set the rendering hints image layout. Only the following properties will be set:
0651: *
0652: * - Color model
0653: * - Tile width
0654: * - Tile height
0655: */
0656: RenderingHints hints = ImageUtilities
0657: .getRenderingHints(parameters.getSource());
0658: ImageLayout layout = (hints != null) ? (ImageLayout) hints
0659: .get(JAI.KEY_IMAGE_LAYOUT) : null;
0660: if (layout == null
0661: || !layout.isValid(ImageLayout.COLOR_MODEL_MASK)) {
0662: if (sampleDims != null && sampleDims.length != 0) {
0663: if (layout == null) {
0664: layout = new ImageLayout();
0665: }
0666: int visibleBand = CoverageUtilities
0667: .getVisibleBand(primarySource
0668: .getRenderedImage());
0669: if (visibleBand >= sampleDims.length) {
0670: visibleBand = 0;
0671: }
0672: final ColorModel colors;
0673: colors = sampleDims[visibleBand].getColorModel(
0674: visibleBand, sampleDims.length);
0675: layout = layout.setColorModel(colors);
0676: }
0677: }
0678: if (layout != null) {
0679: if (hints == null) {
0680: hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
0681: } else {
0682: hints.put(JAI.KEY_IMAGE_LAYOUT, layout);
0683: }
0684: }
0685: if (parameters.hints != null) {
0686: if (hints != null) {
0687: hints.add(parameters.hints); // May overwrite the image layout we have just set.
0688: } else {
0689: hints = parameters.hints;
0690: }
0691: }
0692: /*
0693: * Performs the operation using JAI and construct the new grid coverage.
0694: * Uses the coordinate system from the main source coverage in order to
0695: * preserve the extra dimensions (if any). The first two dimensions should
0696: * be equal to the coordinate system set in the 'parameters' block.
0697: */
0698: final InternationalString name = deriveName(sources,
0699: primarySourceIndex, parameters);
0700: final CoordinateReferenceSystem crs = primarySource
0701: .getCoordinateReferenceSystem();
0702: final MathTransform toCRS = primarySource.getGridGeometry()
0703: .getGridToCRS();
0704: final RenderedImage data = createRenderedImage(
0705: parameters.parameters, hints);
0706: final Map properties = getProperties(data, crs, name, toCRS,
0707: sources, parameters);
0708: return getFactory(parameters.hints).create(name, // The grid coverage name
0709: data, // The underlying data
0710: crs, // The coordinate system (may not be 2D).
0711: toCRS, // The grid transform (may not be 2D).
0712: sampleDims, // The sample dimensions
0713: sources, // The source grid coverages.
0714: properties); // Properties
0715: }
0716:
0717: /**
0718: * Prepares the properties to be given to the coverage created by the
0719: * {@link #deriveGridCoverage deriveGridCoverage} method. The default
0720: * implementation returns {@code null}.
0721: *
0722: * @param data
0723: * The {@link RenderedImage} created by this operation.
0724: * @param crs
0725: * The coordinate reference system assigned to the coverage this
0726: * {@code OperationJAI} will produce.
0727: * @param name
0728: * The name assigned to the coverage this {@code OperationJAI} will produce.
0729: * @param gridToCRS
0730: * The {@linkplain MathTransform transform} from grid to {@code crs} to be
0731: * assigned to the coverage this {@link OperationJAI} will produce.
0732: * @param sources
0733: * The sources to be assigned to the coverage this {@link OperationJAI} will
0734: * produce.
0735: * @param parameters
0736: * The parameters that were used by this {@link OperationJAI}.
0737: * @return a {@link Map} with the properties generated by this
0738: * {@link OperationJAI} or null if we haven't any.
0739: *
0740: * @since 2.4
0741: */
0742: protected Map getProperties(RenderedImage data,
0743: CoordinateReferenceSystem crs, InternationalString name,
0744: MathTransform gridToCRS, GridCoverage2D[] sources,
0745: Parameters parameters) {
0746: return null;
0747: }
0748:
0749: /**
0750: * Returns the index of the quantitative category, providing that there is
0751: * one and only one quantitative category. If {@code categories} contains 0,
0752: * 2 or more quantative category, then this method returns {@code -1}.
0753: *
0754: * @since 2.4
0755: */
0756: protected static int getQuantitative(final Category[] categories) {
0757: int index = -1;
0758: for (int i = 0; i < categories.length; i++) {
0759: if (categories[i].isQuantitative()) {
0760: if (index >= 0) {
0761: return -1;
0762: }
0763: index = i;
0764: }
0765: }
0766: return index;
0767: }
0768:
0769: /**
0770: * Returns the {@linkplain GridSampleDimension sample dimensions} for the target
0771: * {@linkplain GridCoverage2D grid coverage}. This method is invoked automatically by
0772: * {@link #deriveGridCoverage deriveGridCoverage} with a {@code bandLists} argument
0773: * initialized as below:
0774: * <p>
0775: * <ul>
0776: * <li>The {@code bandLists} array length is equals to the number of source coverages.</li>
0777: * <li>The <code>bandLists[<var>i</var>]</code> array length is equals to the number of
0778: * sample dimensions in the source coverage <var>i</var>.</li>
0779: * <li>The sample dimension for a band at index <var>band</var> in the source at index
0780: * <var>source</var> is {@code bandLists[source][band]}.</li>
0781: * </ul>
0782: * <p>
0783: * This method shall returns an array with a length equals to the number of bands in the target
0784: * image. If the sample dimensions can't be determined, then this method is allowed to returns
0785: * {@code null}.
0786: * <p>
0787: * The default implementation iterates among all bands and invokes the {@link #deriveCategory
0788: * deriveCategory} and {@link #deriveUnit deriveUnit} methods for each of them. Subclasses
0789: * should override this method if they know a more accurate algorithm for determining sample
0790: * dimensions.
0791: *
0792: * @param bandLists The set of sample dimensions for each source {@link GridCoverage2D}s.
0793: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0794: * @return The sample dimensions for each band in the destination image, or {@code null}
0795: * if unknow.
0796: *
0797: * @see #deriveCategory
0798: * @see #deriveUnit
0799: */
0800: protected GridSampleDimension[] deriveSampleDimension(
0801: final GridSampleDimension[][] bandLists,
0802: final Parameters parameters) {
0803: /*
0804: * Computes the number of bands. Sources with only 1 band are treated as a special case:
0805: * their unique band is applied to all bands in other sources. If sources don't have the
0806: * same number of bands, then this method returns {@code null} since we don't know how to
0807: * handle those cases.
0808: */
0809: int numBands = 1;
0810: for (int i = 0; i < bandLists.length; i++) {
0811: final int nb = bandLists[i].length;
0812: if (nb != 1) {
0813: if (numBands != 1 && nb != numBands) {
0814: return null;
0815: }
0816: numBands = nb;
0817: }
0818: }
0819: /*
0820: * Iterates among all bands. The 'result' array will contains SampleDimensions created
0821: * during the iteration for each individual band. The 'XS' suffix designates temporary
0822: * arrays of categories and units accross all sources for one particular band.
0823: */
0824: final GridSampleDimension[] result = new GridSampleDimension[numBands];
0825: final Category[] categoryXS = new Category[bandLists.length];
0826: final Unit[] unitXS = new Unit[bandLists.length];
0827: while (--numBands >= 0) {
0828: GridSampleDimension sampleDim = null;
0829: Category[] categoryArray = null;
0830: int indexOfQuantitative = 0;
0831: assert PRIMARY_SOURCE_INDEX == 0; // See comment below.
0832: for (int i = bandLists.length; --i >= 0;) {
0833: /*
0834: * Iterates among all sources (i) for the current band. We iterate
0835: * sources in reverse order because the primary source MUST be the
0836: * last one iterated, in order to have proper values for variables
0837: * 'sampleDim', 'categoryArray' and 'indexOfQuantitative' after the
0838: * loop.
0839: */
0840: final GridSampleDimension[] allBands = bandLists[i];
0841: sampleDim = allBands[allBands.length == 1 ? 0
0842: : numBands];
0843: categoryArray = (Category[]) sampleDim.getCategories()
0844: .toArray();
0845: indexOfQuantitative = getQuantitative(categoryArray);
0846: if (indexOfQuantitative < 0) {
0847: return null;
0848: }
0849: unitXS[i] = sampleDim.getUnits();
0850: categoryXS[i] = categoryArray[indexOfQuantitative];
0851: }
0852: final Category oldCategory = categoryArray[indexOfQuantitative];
0853: final Unit oldUnit = sampleDim.getUnits();
0854: final Category newCategory = deriveCategory(categoryXS,
0855: parameters);
0856: final Unit newUnit = deriveUnit(unitXS, parameters);
0857: if (newCategory == null) {
0858: return null;
0859: }
0860: if (!oldCategory.equals(newCategory)
0861: || !Utilities.equals(oldUnit, newUnit)) {
0862: /*
0863: * Create a new sample dimension. Note that we use a null title, not the same
0864: * title than the original sample dimension, because the new sample dimension
0865: * may be quite different. For example the original sample dimension may be
0866: * about "Temperature" in °C units, and the new one about "Gradiant magnitude
0867: * of Temperature" in °C/km units. The GridSampleDimension constructor will
0868: * infers the title from what looks like the "main" category.
0869: */
0870: final CharSequence title = null;
0871: categoryArray[indexOfQuantitative] = newCategory;
0872: result[numBands] = new GridSampleDimension(title,
0873: categoryArray, newUnit);
0874: } else {
0875: // Reuse the category list from the primary source.
0876: result[numBands] = sampleDim;
0877: }
0878: }
0879: return result;
0880: }
0881:
0882: /**
0883: * Returns the quantitative category for a single {@linkplain GridSampleDimension sample dimension}
0884: * in the target {@linkplain GridCoverage2D grid coverage}. This method is invoked automatically
0885: * by the {@link #deriveSampleDimension deriveSampleDimension} method for each band in the
0886: * target image. The default implementation creates a default category from the target range
0887: * of values returned by {@link #deriveRange deriveRange}.
0888: *
0889: * @param categories The quantitative categories from every sources. For unary operations
0890: * like {@code "GradientMagnitude"}, this array has a length of 1. For binary
0891: * operations like {@code "add"} and {@code "multiply"}, this array has a length of 2.
0892: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0893: * @return The quantative category to use in the destination image, or {@code null} if unknow.
0894: */
0895: protected Category deriveCategory(final Category[] categories,
0896: final Parameters parameters) {
0897: final NumberRange[] ranges = new NumberRange[categories.length];
0898: for (int i = 0; i < ranges.length; i++) {
0899: ranges[i] = categories[i].getRange();
0900: }
0901: final NumberRange range = deriveRange(ranges, parameters);
0902: if (range != null) {
0903: final Category category = categories[PRIMARY_SOURCE_INDEX];
0904: return new Category(category.getName(), category
0905: .getColors(),
0906: category.geophysics(false).getRange(), range)
0907: .geophysics(true);
0908: }
0909: return null;
0910: }
0911:
0912: /**
0913: * Returns the range of value for a single {@linkplain GridSampleDimension sample dimension}
0914: * in the target {@linkplain GridCoverage2D grid coverage}. This method is invoked automatically
0915: * by the {@link #deriveCategory deriveCategory} method for each band in the target image.
0916: * Subclasses should override this method in order to compute the target range of values.
0917: * For example, the {@code "add"} operation may implements this method as below:
0918: *
0919: * <blockquote><pre>
0920: * double min = ranges[0].getMinimum() + ranges[1].getMinimum();
0921: * double max = ranges[0}.getMaximum() + ranges[1}.getMaximum();
0922: * return new NumberRange(min, max);
0923: * </pre></blockquote>
0924: *
0925: * @param ranges The range of values from every sources. For unary operations like
0926: * {@code "GradientMagnitude"}, this array has a length of 1. For binary operations
0927: * like {@code "add"} and {@code "multiply"}, this array has a length of 2.
0928: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0929: * @return The range of values to use in the destination image, or {@code null} if unknow.
0930: */
0931: protected NumberRange deriveRange(final NumberRange[] ranges,
0932: final Parameters parameters) {
0933: return null;
0934: }
0935:
0936: /**
0937: * Returns the unit of data for a single {@linkplain GridSampleDimension sample dimension} in the
0938: * target {@linkplain GridCoverage2D grid coverage}. This method is invoked automatically by
0939: * the {@link #deriveSampleDimension deriveSampleDimension} method for each band in the target
0940: * image. Subclasses should override this method in order to compute the target units from the
0941: * source units. For example a {@code "multiply"} operation may implement this method as below:
0942: *
0943: * <blockquote><pre>
0944: * if (units[0]!=null && units[1]!=null) {
0945: * return units[0].{@link Unit#multiply(Unit) multiply}(units[1]);
0946: * } else {
0947: * return super.deriveUnit(units, cs, parameters);
0948: * }
0949: * </pre></blockquote>
0950: *
0951: * @param units The units from every sources. For unary operations like
0952: * {@code "GradientMagnitude"}, this array has a length of 1. For binary operations
0953: * like {@code "add"} and {@code "multiply"}, this array has a length of 2.
0954: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0955: * @return The unit of data in the destination image, or {@code null} if unknow.
0956: */
0957: protected Unit deriveUnit(final Unit[] units,
0958: final Parameters parameters) {
0959: return null;
0960: }
0961:
0962: /**
0963: * Returns a name for the target {@linkplain GridCoverage2D grid coverage} based on the given
0964: * sources. This method is invoked once by the {@link #deriveGridCoverage deriveGridCoverage}
0965: * method. The default implementation returns the operation name followed by the source name
0966: * between parenthesis, for example "<cite>GradientMagnitude(Sea Surface Temperature)</cite>".
0967: *
0968: * @param sources The sources grid coverage.
0969: * @param primarySourceIndex The index of what seems to be the primary source, or {@code -1}
0970: * if none of unknow.
0971: * @param parameters Parameters, rendering hints and coordinate reference system to use.
0972: * @return A name for the target grid coverage.
0973: */
0974: protected InternationalString deriveName(
0975: final GridCoverage2D[] sources,
0976: final int primarySourceIndex, final Parameters parameters) {
0977: final InternationalString[] names;
0978: if (primarySourceIndex >= 0) {
0979: names = new InternationalString[] { sources[primarySourceIndex]
0980: .getName() };
0981: } else {
0982: names = new InternationalString[sources.length];
0983: for (int i = 0; i < names.length; i++) {
0984: names[i] = sources[i].getName();
0985: }
0986: }
0987: return new Name(getName(), names);
0988: }
0989:
0990: /**
0991: * A localized name for the default implementation of {@link OperationJAI#deriveName}.
0992: */
0993: private static final class Name extends AbstractInternationalString
0994: implements Serializable {
0995: /** Serial number for cross-versions compatibility. */
0996: private static final long serialVersionUID = -8096255331549347383L;
0997:
0998: /** The operation name. */
0999: private final String operation;
1000:
1001: /** Names of source grid coverages. */
1002: private final InternationalString[] sources;
1003:
1004: /** Constructs a name from the given source names. */
1005: public Name(final String operation,
1006: final InternationalString[] sources) {
1007: this .operation = operation;
1008: this .sources = sources;
1009: }
1010:
1011: /** Returns a string localized in the given locale. */
1012: public String toString(final Locale locale) {
1013: final StringBuffer buffer = new StringBuffer(operation);
1014: buffer.append('(');
1015: for (int i = 0; i < sources.length; i++) {
1016: if (i != 0) {
1017: buffer.append(", ");
1018: }
1019: buffer.append(sources[i].toString(locale));
1020: }
1021: buffer.append(')');
1022: return buffer.toString();
1023: }
1024: }
1025:
1026: /**
1027: * Applies the JAI operation. The operation name can be fetch from {@link #operation}.
1028: * The JAI instance to use can be fetch from {@link #getJAI}. The default implementation
1029: * returns the following:
1030: *
1031: * <blockquote><pre>
1032: * {@linkplain #getJAI getJAI}(hints).{@linkplain JAI#createNS createNS}({@linkplain #operation}.getName(), parameters, hints)
1033: * </pre></blockquote></li>
1034: *
1035: * Subclasses may override this method in order to invokes a different JAI operation
1036: * according the parameters.
1037: *
1038: * @param parameters The parameters to be given to JAI.
1039: * @param hints The rendering hints to be given to JAI.
1040: */
1041: protected RenderedImage createRenderedImage(
1042: final ParameterBlockJAI parameters,
1043: final RenderingHints hints) {
1044: return getJAI(hints).createNS(operation.getName(), parameters,
1045: hints);
1046: }
1047:
1048: /**
1049: * Returns the {@link JAI} instance to use for operations on {@link RenderedImage}.
1050: * If no JAI instance is defined for the {@link Hints#JAI_INSTANCE} key, then the
1051: * default instance is returned.
1052: *
1053: * @param hints The rendering hints, or {@code null} if none.
1054: * @return The JAI instance to use (never {@code null}).
1055: */
1056: public static JAI getJAI(final RenderingHints hints) {
1057: if (hints != null) {
1058: final Object value = hints.get(Hints.JAI_INSTANCE);
1059: if (value instanceof JAI) {
1060: return (JAI) value;
1061: }
1062: }
1063: return JAI.getDefaultInstance();
1064: }
1065:
1066: /**
1067: * Compares the specified object with this operation for equality.
1068: */
1069: public boolean equals(final Object object) {
1070: if (object == this ) {
1071: // Slight optimisation
1072: return true;
1073: }
1074: if (super .equals(object)) {
1075: final OperationJAI that = (OperationJAI) object;
1076: return Utilities.equals(this .operation, that.operation);
1077: }
1078: return false;
1079: }
1080:
1081: /**
1082: * A block of parameters for a {@link GridCoverage2D} processed by a {@link OperationJAI}.
1083: * This parameter is given to the following methods:
1084: *
1085: * <ul>
1086: * <li>{@link OperationJAI#deriveSampleDimension deriveSampleDimension}</li>
1087: * <li>{@link OperationJAI#deriveCategory deriveCategory}</li>
1088: * <li>{@link OperationJAI#deriveUnit deriveUnit}</li>
1089: * </ul>
1090: *
1091: * @since 2.2
1092: * @version $Id: OperationJAI.java 27848 2007-11-12 13:10:32Z desruisseaux $
1093: * @author Martin Desruisseaux
1094: */
1095: protected static final class Parameters {
1096: /**
1097: * The two dimensional coordinate reference system for all sources and the
1098: * destination {@link GridCoverage2D}. Sources coverages will be projected in
1099: * this CRS as needed.
1100: */
1101: public final CoordinateReferenceSystem crs;
1102:
1103: /**
1104: * The "grid to coordinate reference system" transform common to all source grid coverages.
1105: */
1106: public final MathTransform2D gridToCRS;
1107:
1108: /**
1109: * The parameters to be given to the {@link JAI#createNS} method.
1110: */
1111: public final ParameterBlockJAI parameters;
1112:
1113: /**
1114: * The rendering hints to be given to the {@link JAI#createNS} method.
1115: * The {@link JAI} instance to use for the {@code createNS} call will
1116: * be fetch from the {@link Hints#JAI_INSTANCE} key.
1117: */
1118: public final Hints hints;
1119:
1120: /**
1121: * Constructs a new parameter block with the specified values.
1122: */
1123: Parameters(final CoordinateReferenceSystem crs,
1124: final MathTransform2D gridToCRS,
1125: final ParameterBlockJAI parameters, final Hints hints) {
1126: this .crs = crs;
1127: this .gridToCRS = gridToCRS;
1128: this .parameters = parameters;
1129: this .hints = hints;
1130: }
1131:
1132: /**
1133: * Returns the first source image, or {@code null} if none.
1134: */
1135: final RenderedImage getSource() {
1136: final int n = parameters.getNumSources();
1137: for (int i = 0; i < n; i++) {
1138: final Object source = parameters.getSource(i);
1139: if (source instanceof RenderedImage) {
1140: return (RenderedImage) source;
1141: }
1142: }
1143: return null;
1144: }
1145: }
1146: }
|