/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2001-2008, Open Source Geospatial Foundation (OSGeo)
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation;
* version 2.1 of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*/
package org.geotools.coverage.processing;
import java.awt.RenderingHints;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.io.Serializable;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.Locale;
import java.util.Collections;
import javax.measure.unit.Unit;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.OperationRegistry;
import javax.media.jai.OperationDescriptor;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.registry.RenderedRegistryMode;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.processing.OperationNotFoundException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.IdentifiedObject;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.MathTransformFactory;
import org.opengis.referencing.operation.TransformException;
import org.opengis.parameter.ParameterDescriptorGroup;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.util.InternationalString;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.grid.ViewType;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.InvalidGridGeometryException;
import org.geotools.factory.Hints;
import org.geotools.parameter.ImagingParameters;
import org.geotools.parameter.ImagingParameterDescriptors;
import org.geotools.referencing.CRS;
import org.geotools.referencing.ReferencingFactoryFinder;
import org.geotools.referencing.operation.transform.DimensionFilter;
import org.geotools.image.jai.Registry;
import org.geotools.resources.XArray;
import org.geotools.resources.CRSUtilities;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.util.AbstractInternationalString;
import org.geotools.util.Utilities;
import org.geotools.util.NumberRange;
import org.geotools.util.logging.Logging;
/**
* Wraps a JAI's {@link OperationDescriptor} for interoperability with
* Java Advanced Imaging.
* This class help to leverage the rich set of JAI operators in an GeoAPI framework.
* {@code OperationJAI} inherits operation name and argument types from {@link OperationDescriptor},
* except the source argument type (usually {@linkplain RenderedImage}.class
) which is
* set to {@linkplain GridCoverage2D}.class
. If there is only one source argument, it
* will be renamed {@code "source"} for better compliance with OpenGIS usage.
*
* The entry point for applying an operation is the usual {@link #doOperation doOperation} method. * The default implementation forward the call to other methods for different bits of tasks, * resulting in the following chain of calls: *
*
* * @since 2.2 * * * @source $URL$ * @version $Id$ * @author Martin Desruisseaux (IRD) * @author Simone Giannecchini */ public class OperationJAI extends Operation2D { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -5974520239347639965L; /** * The rendered mode for JAI operation. */ protected static final String RENDERED_MODE = RenderedRegistryMode.MODE_NAME; /** * The JAI's operation descriptor. */ protected final OperationDescriptor operation; /** * Constructs a grid coverage operation from a JAI operation name. This convenience * constructor fetch the {@link OperationDescriptor} from the specified operation * name using the default {@link JAI} instance. * * @param operation JAI operation name (e.g. {@code "GradientMagnitude"}). * @throws OperationNotFoundException if no JAI descriptor was found for the given name. */ public OperationJAI(final String operation) throws OperationNotFoundException { this(getOperationDescriptor(operation)); } /** * Constructs a grid coverage operation backed by a JAI operation. The operation descriptor * must supports the {@code "rendered"} mode (which is the case for most JAI operations). * * @param operation The JAI operation descriptor. */ public OperationJAI(final OperationDescriptor operation) { this(operation, new ImagingParameterDescriptors(operation)); } /** * Constructs a grid coverage operation backed by a JAI operation. The operation descriptor * must supports the {@code "rendered"} mode (which is the case for most JAI operations). * * @param operation The JAI operation descriptor. * @param descriptor The OGC parameters descriptor. */ protected OperationJAI(final OperationDescriptor operation, final ParameterDescriptorGroup descriptor) { super(descriptor); this.operation = operation; Utilities.ensureNonNull("operation", operation); /* * Check argument validity. */ ensureRenderedImage(operation.getDestClass(RENDERED_MODE)); final Class[] sourceClasses = operation.getSourceClasses(RENDERED_MODE); if (sourceClasses != null) { final int length = sourceClasses.length; assert length == operation.getNumSources(); for (int i=0; i*
* {@link #doOperation doOperation}: *the entry point. * {@link #resampleToCommonGeometry resampleToCommonGeometry}: *reprojects all sources to the same coordinate reference system. * {@link #deriveGridCoverage deriveGridCoverage}: *gets the destination properties. * {@link #deriveSampleDimension deriveSampleDimension}: *gets the destination sample dimensions. * {@link #deriveCategory deriveCategory}: *gets the destination categories. * {@link #deriveRange deriveRange}: *gets the expected range of values. * {@link #deriveUnit deriveUnit}: *gets the destination units. * {@link #createRenderedImage createRenderedImage}: *the actual call to {@link JAI#createNS JAI.createNS}.
* Note: it would be possible to use {@link ImagingParameters#parameters} * directly in some occasions. However, we perform an unconditional copy instead * because some operations (e.g. "GradientMagnitude") may change the values. * * @param parameters The {@link ParameterValueGroup} to be copied. * @return A copy of the provided {@link ParameterValueGroup} as a JAI block. * * @since 2.4 */ protected ParameterBlockJAI prepareParameters(final ParameterValueGroup parameters) { final ImagingParameters copy = (ImagingParameters) descriptor.createValue(); final ParameterBlockJAI block = (ParameterBlockJAI) copy.parameters; org.geotools.parameter.Parameters.copy(parameters, copy); return block; } /** * Applies a process operation to a grid coverage. * The default implementation performs the following steps: * *
{@linkplain GridCoverage2D#geophysics GridCoverage2D.geophysics}(true)
.
* This allow to performs all computation on geophysics values instead of encoded
* samples. Note: this step is disabled if
* {@link #computeOnGeophysicsValues computeOnGeophysicsValues} returns
* {@code false}.{@linkplain GridCoverage2D#geophysics GridCoverage2D.geophysics}(false)
.
* * Subclasses should override this method if they want to specify target * {@linkplain GridGeometry2D grid geometry} and * {@linkplain CoordinateReferenceSystem coordinate reference system} different than the * default ones. For example if a subclass wants to force all images to be referenced in a * {@linkplain org.geotools.referencing.crs.DefaultGeographicCRS#WGS84 WGS 84} CRS, then * it may overrides this method as below: * *
* * @param sources The source grid coverages to resample. This array is updated in-place as * needed (for example if a grid coverage is replaced by a projected one). * @param crs2D The target coordinate reference system to use, or {@code null} for a * default one. * @param gridToCrs2D The target "grid to coordinate reference system" transform, or * {@code null} for a default one. * @param hints The rendering hints, or {@code null} if none. * * @throws InvalidGridGeometryException if a source coverage has an unsupported grid geometry. * @throws CannotReprojectException if a grid coverage can't be resampled for some other reason. */ protected void resampleToCommonGeometry(final GridCoverage2D[] sources, CoordinateReferenceSystem crs2D, MathTransform2D gridToCrs2D, final Hints hints) throws InvalidGridGeometryException, CannotReprojectException { if (sources==null || sources.length==0) { return; // Nothing to reproject. } /* * Ensures that the target CRS is two-dimensional. If no target CRS were specified, * uses the CRS of the primary source. The math transform must be 2D too, but this * is ensured by the interface type (MathTransform2D). */ final GridCoverage2D primarySource = sources[PRIMARY_SOURCE_INDEX]; if (crs2D == null) { if (gridToCrs2D==null && sources.length==1) { return; // No need to reproject. } crs2D = primarySource.getCoordinateReferenceSystem2D(); } else try { crs2D = CRSUtilities.getCRS2D(crs2D); } catch (TransformException exception) { // TODO: localize throw new CannotReprojectException("Unsupported CRS: "+crs2D.getName().getCode()); } if (gridToCrs2D == null) { gridToCrs2D = primarySource.getGridGeometry().getGridToCRS2D(); } /* * 'crs2D' is the two dimensional part of the target CRS. Now for each source coverages, * substitute their two-dimensional CRS by this 'crs2D'. A source may have more than two * dimensions. For example it may have a time or a depth axis. In such case, their "head" * and "tail" CRS will be preserved before and after 'crs2D'. */ final CoverageProcessor processor = CoverageProcessor.getInstance(hints); for (int i=0; i* protected void resampleToCommonGeometry(...) { * crs2D = DefaultGeographicCRS.WGS84; * super.resampleToCommonGeometry(sources, crs2D, gridToCrs2D, hints); * }
*
bandLists[i]
array length is equals to the number of
* sample dimensions in the source coverage i.* This method shall returns an array with a length equals to the number of bands in the target * image. If the sample dimensions can't be determined, then this method is allowed to returns * {@code null}. *
* The default implementation iterates among all bands and invokes the {@link #deriveCategory
* deriveCategory} and {@link #deriveUnit deriveUnit} methods for each of them. Subclasses
* should override this method if they know a more accurate algorithm for determining sample
* dimensions.
*
* @param bandLists The set of sample dimensions for each source {@link GridCoverage2D}s.
* @param parameters Parameters, rendering hints and coordinate reference system to use.
* @return The sample dimensions for each band in the destination image, or {@code null}
* if unknown.
*
* @see #deriveCategory
* @see #deriveUnit
*/
protected GridSampleDimension[] deriveSampleDimension(final GridSampleDimension[][] bandLists,
final Parameters parameters)
{
/*
* Computes the number of bands. Sources with only 1 band are treated as a special case:
* their unique band is applied to all bands in other sources. If sources don't have the
* same number of bands, then this method returns {@code null} since we don't know how to
* handle those cases.
*/
int numBands = 1;
for (int i=0; i
* double min = ranges[0].getMinimum() + ranges[1].getMinimum();
* double max = ranges[0}.getMaximum() + ranges[1}.getMaximum();
* return new NumberRange(min, max);
*
*
* @param ranges The range of values from every sources. For unary operations like
* {@code "GradientMagnitude"}, this array has a length of 1. For binary operations
* like {@code "add"} and {@code "multiply"}, this array has a length of 2.
* @param parameters Parameters, rendering hints and coordinate reference system to use.
* @return The range of values to use in the destination image, or {@code null} if unknow.
*/
protected NumberRange deriveRange(final NumberRange[] ranges, final Parameters parameters) {
return null;
}
/**
* Returns the unit of data for a single {@linkplain GridSampleDimension sample dimension} in the
* target {@linkplain GridCoverage2D grid coverage}. This method is invoked automatically by
* the {@link #deriveSampleDimension deriveSampleDimension} method for each band in the target
* image. Subclasses should override this method in order to compute the target units from the
* source units. For example a {@code "multiply"} operation may implement this method as below:
*
*
*
* @param units The units from every sources. For unary operations like
* {@code "GradientMagnitude"}, this array has a length of 1. For binary operations
* like {@code "add"} and {@code "multiply"}, this array has a length of 2.
* @param parameters Parameters, rendering hints and coordinate reference system to use.
* @return The unit of data in the destination image, or {@code null} if unknow.
*/
protected Unit> deriveUnit(final Unit>[] units, final Parameters parameters) {
return null;
}
/**
* Returns a name for the target {@linkplain GridCoverage2D grid coverage} based on the given
* sources. This method is invoked once by the {@link #deriveGridCoverage deriveGridCoverage}
* method. The default implementation returns the operation name followed by the source name
* between parenthesis, for example "GradientMagnitude(Sea Surface Temperature)".
*
* @param sources The sources grid coverage.
* @param primarySourceIndex The index of what seems to be the primary source, or {@code -1}
* if none of unknown.
* @param parameters Parameters, rendering hints and coordinate reference system to use.
* @return A name for the target grid coverage.
*/
protected InternationalString deriveName(final GridCoverage2D[] sources,
final int primarySourceIndex,
final Parameters parameters)
{
final InternationalString[] names;
if (primarySourceIndex >= 0) {
names = new InternationalString[] {
sources[primarySourceIndex].getName()
};
} else {
names = new InternationalString[sources.length];
for (int i=0; i
* if (units[0]!=null && units[1]!=null) {
* return units[0].{@link Unit#multiply(Unit) multiply}(units[1]);
* } else {
* return super.deriveUnit(units, cs, parameters);
* }
*
* {@linkplain #getJAI getJAI}(hints).{@linkplain JAI#createNS createNS}({@linkplain #operation}.getName(), parameters, hints)
*
*
* Subclasses may override this method in order to invokes a different JAI operation
* according the parameters.
*
* @param parameters The parameters to be given to JAI.
* @param hints The rendering hints to be given to JAI.
* @return The result of JAI operation using the given parameters and hints.
*/
protected RenderedImage createRenderedImage(final ParameterBlockJAI parameters,
final RenderingHints hints)
{
return getJAI(hints).createNS(operation.getName(), parameters, hints);
}
/**
* Returns the {@link JAI} instance to use for operations on {@link RenderedImage}.
* If no JAI instance is defined for the {@link Hints#JAI_INSTANCE} key, then the
* default instance is returned.
*
* @param hints The rendering hints, or {@code null} if none.
* @return The JAI instance to use (never {@code null}).
*/
public final static JAI getJAI(final RenderingHints hints) {
if (hints != null) {
final Object value = hints.get(Hints.JAI_INSTANCE);
if (value instanceof JAI) {
return (JAI) value;
}
}
return JAI.getDefaultInstance();
}
// /**
// *
// * @param polygon
// * @param worldToGridTransform
// * @return
// * @throws FactoryException
// * @throws TransformException
// */
// public final static ROI polygonToRoi(final Polygon polygon,
// final MathTransform worldToGridTransform) throws TransformException,
// FactoryException {
//
// return new ROIShape(new LiteShape2(polygon, worldToGridTransform, null, false));
// }
//
// /**
// *
// * @param polygon
// * @param transform
// * @return
// * @throws FactoryException
// * @throws TransformException
// */
// public final static ROI polygonToRoi(final Polygon polygon,
// final AffineTransform worldToGridTransform) throws TransformException,
// FactoryException {
//
// return polygonToRoi(polygon, ProjectiveTransform.create(worldToGridTransform));
// }
//
/**
* Compares the specified object with this operation for equality.
*/
@Override
public boolean equals(final Object object) {
if (object == this) {
// Slight optimisation
return true;
}
if (super.equals(object)) {
final OperationJAI that = (OperationJAI) object;
return Utilities.equals(this.operation, that.operation);
}
return false;
}
/**
* A block of parameters for a {@link GridCoverage2D} processed by a {@link OperationJAI}.
* This parameter is given to the following methods:
*
*
*
*
* @since 2.2
* @version $Id$
* @author Martin Desruisseaux (IRD)
*/
protected static final class Parameters {
/**
* The two dimensional coordinate reference system for all sources and the
* destination {@link GridCoverage2D}. Sources coverages will be projected in
* this CRS as needed.
*/
public final CoordinateReferenceSystem crs;
/**
* The "grid to coordinate reference system" transform common to all source grid coverages.
*/
public final MathTransform2D gridToCRS;
/**
* The parameters to be given to the {@link JAI#createNS} method.
*/
public final ParameterBlockJAI parameters;
/**
* The rendering hints to be given to the {@link JAI#createNS} method.
* The {@link JAI} instance to use for the {@code createNS} call will
* be fetch from the {@link Hints#JAI_INSTANCE} key.
*/
public final Hints hints;
/**
* Constructs a new parameter block with the specified values.
*/
Parameters(final CoordinateReferenceSystem crs,
final MathTransform2D gridToCRS,
final ParameterBlockJAI parameters,
final Hints hints)
{
this.crs = crs;
this.gridToCRS = gridToCRS;
this.parameters = parameters;
this.hints = hints;
}
/**
* Returns the first source image, or {@code null} if none.
*/
final RenderedImage getSource() {
final int n = parameters.getNumSources();
for (int i=0; i