/* * Geotools 2 - OpenSource mapping toolkit * (C) 2003, Geotools Project Management Committee (PMC) * (C) 2001, Institut de Recherche pour le Développement * * 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; either * version 2.1 of the License, or (at your option) any later version. * * 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. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * * This package contains documentation from OpenGIS specifications. * OpenGIS consortium's work is fully acknowledged here. * * @deprecated Replaced by {@link org.geotools.coverage.processing.OperationJAI}. */ package org.geotools.gp; // J2SE dependencies import java.awt.RenderingHints; import java.awt.image.ColorModel; import java.awt.image.RenderedImage; import java.util.Arrays; import java.util.Locale; import java.util.MissingResourceException; import javax.media.jai.ImageLayout; import javax.media.jai.IntegerSequence; import javax.media.jai.JAI; import javax.media.jai.OperationDescriptor; import javax.media.jai.ParameterBlockJAI; import javax.media.jai.ParameterList; import javax.media.jai.ParameterListDescriptor; import javax.media.jai.ParameterListDescriptorImpl; import javax.media.jai.registry.RenderedRegistryMode; import javax.media.jai.util.Range; import org.geotools.cs.CompoundCoordinateSystem; import org.geotools.cs.CoordinateSystem; import org.geotools.ct.CoordinateTransformationFactory; import org.geotools.ct.MathTransform; import org.geotools.ct.MathTransform2D; import org.geotools.ct.MathTransformFactory; import org.geotools.cv.Category; import org.geotools.cv.SampleDimension; import org.geotools.gc.GridCoverage; import org.geotools.gc.GridGeometry; import org.geotools.gc.InvalidGridGeometryException; import org.geotools.resources.CTSUtilities; import org.geotools.resources.LegacyGCSUtilities; import org.geotools.resources.Utilities; import org.geotools.resources.i18n.ErrorKeys; import org.geotools.resources.i18n.Errors; import org.geotools.resources.image.ImageUtilities; import org.geotools.resources.image.JAIUtilities; import org.geotools.units.Unit; import org.opengis.referencing.FactoryException; /** * Wrap an {@link OperationDescriptor} for interoperability with * Java Advanced Imaging. * This class help to leverage the rich set of JAI operators in an OpenGIS framework. * OperationJAI inherits operation name and argument types from * {@link OperationDescriptor}, except source argument type which is set to * GridCoverage.class. If there is only one source argument, il will be * renamed "Source" for better compliance to OpenGIS usage. *

* The entry point for applying 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: * *
    *
  1. {@link #doOperation doOperation} (the entry point)
  2. *
  3. {@link #resampleToCommonGeometry resampleToCommonGeometry} * (reproject all source to the same coordinate system)
  4. *
  5. {@link #deriveGridCoverage deriveGridCoverage} (gets the destination properties)
  6. *
  7. {@link #deriveSampleDimension} (gets the destination sample dimensions)
  8. *
  9. {@link #deriveCategory} (gets the destination categories)
  10. *
  11. {@link #deriveUnit} (gets the destination units)
  12. *
  13. {@link #createRenderedImage} (the actual call to {@link JAI#createNS JAI.createNS})
  14. *
* * @source $URL$ * @version $Id$ * @author Martin Desruisseaux */ public class OperationJAI extends Operation { /** * Serial number for interoperability with different versions. */ private static final long serialVersionUID = -5974520239347639965L; /** * The rendered mode for JAI operation. */ private static final String RENDERED_MODE = RenderedRegistryMode.MODE_NAME; /** * Index of the source {@link GridCoverage} to use as a model. The * destination grid coverage will reuse the same coordinate system, * envelope and qualitative categories than this "master" source. *

* For operations expecting only one source, there is no ambiguity. * But for operations expecting more than one source, the choice of * a "master" source is somewhat arbitrary. This constant is used * merely as a flag for spotting those places in the code. */ private static final int MASTER_SOURCE_INDEX = 0; /** * The operation descriptor. */ protected final OperationDescriptor descriptor; /** * Construct an OpenGIS 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 operationName JAI operation name (e.g. "GradientMagnitude"). * @throws OperationNotFoundException if no JAI descriptor was found for the given name. */ public OperationJAI(final String operationName) throws OperationNotFoundException { this(getOperationDescriptor(operationName)); } /** * Construct an OpenGIS operation from a JAI operation descriptor. * The OpenGIS operation will have the same name than the JAI operation. * * @param descriptor The operation descriptor. This descriptor * must supports the "rendered" mode (which is the case * for most JAI operations). */ public OperationJAI(final OperationDescriptor descriptor) { this(null, descriptor, null); } /** * Construct an OpenGIS operation backed by a JAI operation. * * @param name The operation name for {@link GridCoverageProcessor} registration. * May or may not be the same than the JAI operation name. If null, * then the JAI operation name is used. * @param operationDescriptor The operation descriptor. This descriptor must supports * supports the "rendered" mode (which is the case for most JAI operations). * @param paramDescriptor The parameters descriptor. If null, * then it will be infered from the JAI's parameter descriptor. * * @throws NullPointerException if operationDescriptor is null. */ protected OperationJAI(final String name, final OperationDescriptor operationDescriptor, final ParameterListDescriptor paramDescriptor) { super((name!=null) ? name : getName(operationDescriptor), (paramDescriptor!=null) ? paramDescriptor : getParameterListDescriptor(operationDescriptor)); this.descriptor = operationDescriptor; } /** * Returns a name from the specified operation descriptor. If the * name begin with "org.geotools" prefix, the prefix will be ignored. * * @task TODO: Should be inlined in the constructor if only Sun was to fix RFE #4093999 * ("Relax constraint on placement of this()/super() call in constructors"). */ private static String getName(final OperationDescriptor descriptor) { final String prefix = "org.geotools."; String name = descriptor.getName(); if (name.startsWith(prefix)) { name = name.substring(prefix.length()); } return name; } /** * Returns the operation descriptor for the specified JAI operation name. * This method uses the default {@link JAI} instance and looks for the * "rendered" mode. * * @param name The operation name. * @return The operation descriptor for the given name. * @throws OperationNotFoundException if no JAI descriptor was found for the given name. */ protected static OperationDescriptor getOperationDescriptor(final String name) throws OperationNotFoundException { final OperationDescriptor descriptor = (OperationDescriptor) JAI.getDefaultInstance(). getOperationRegistry().getDescriptor(RENDERED_MODE, name); if (descriptor == null) { throw new OperationNotFoundException(Errors.format( ErrorKeys.OPERATION_NOT_FOUND_$1, name)); } return descriptor; } /** * Gets the parameter list descriptor for an operation descriptor. * {@link OperationDescriptor} parameter list do not include sources. * This method will add them in front of the parameter list. */ private static ParameterListDescriptor getParameterListDescriptor (final OperationDescriptor descriptor) { ensureValid(descriptor.getDestClass(RENDERED_MODE)); final Class[] sourceClasses = descriptor.getSourceClasses(RENDERED_MODE); if (sourceClasses != null) { for (int i=0; inull. */ private static int length(final Object[] array) { return (array!=null) ? array.length : 0; } /** * Returns the coordinate system for the specified coverage. Since the default implementation * of {@link GridCoverage} ignores all dimensions after the 2 first, this method returns only * the coordinate system for the 2 first dimensions. * * @param coverage The grid coverage to get the coordinate system. * @return The coordinate system for the 2 first dimensions. * @throws InvalidGridGeometryException if this method can't get the CS for the two first * dimensions. */ private static CoordinateSystem getCoordinateSystem(final GridCoverage coverage) throws InvalidGridGeometryException { CoordinateSystem cs = coverage.getCoordinateSystem(); cs = CTSUtilities.getSubCoordinateSystem(cs, 0, 2); if (cs != null) { return cs; } // TODO: provides a localized message. throw new InvalidGridGeometryException(); } /** * Check if array names contains the element name. * Search is done in case-insensitive manner. This method is efficient enough * if names is very short (less than 10 entries). */ private static boolean contains(final String[] names, final String name) { for (int i=0; inull. The default implementation fetch the description from * the {@linkplain #descriptor}. * * @param locale The desired locale, or null for the default locale. */ public String getDescription(Locale locale) { if (locale == null) { locale = Locale.getDefault(); } try { return descriptor.getResourceBundle(locale).getString("Description"); } catch (MissingResourceException exception) { // No description available. Returns 'null', which is // a legal value according this method specification. return null; } } /** * Returns the number of source grid coverages required for the operation. The * default implementation fetch the information from the {@linkplain #descriptor}. */ public int getNumSources() { return descriptor.getNumSources(); } /** * Set a parameter. This method can be overriden in order to apply some conversions * from OpenGIS to JAI parameters. * * @param block The parameter block in which to set a parameter. * @param name The parameter OpenGIS name. * @param value The parameter OpenGIS value. */ void setParameter(final ParameterBlockJAI block, final String name, final Object value) { block.setParameter(name, value); } /** * Returns true if grid coverage should be transformed from sample values * to geophysics value before to apply an operation. */ boolean computeOnGeophysicsValues() { return true; } /** * Apply a process operation to a grid coverage. * The default implementation performs the following steps: * * * * @param parameters List of name value pairs for the parameters required for the operation. * @param hints A set of rendering hints, or null if none. * @return The result as a grid coverage. * * @see #deriveGridCoverage */ protected GridCoverage doOperation(final ParameterList parameters, final RenderingHints hints) { /* * Copy parameter values from the ParameterList to the ParameterBlockJAI. * The sources GridCoverages are extracted in the process and the source * RenderedImage are set in the ParameterBlockJAI. The first array of * range specifiers, if any, is treated especialy. */ RangeSpecifier[] ranges = null; Boolean requireGeophysicsType = null; final ParameterBlockJAI block = new ParameterBlockJAI(descriptor, RENDERED_MODE); final String[] paramNames = parameters.getParameterListDescriptor().getParamNames(); final String[] blockParamNames = block.getParameterListDescriptor().getParamNames(); final String[] sourceNames = getSourceNames(parameters); final GridCoverage[] sources = new GridCoverage[length(sourceNames)]; for (int srcCount=0,i=0; inull for a default one. * @param gridToCS The target "grid to coordinate system" transform, * or null for a default one. * @param hints The rendering hints, or 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 GridCoverage[] sources, CoordinateSystem coordinateSystem, MathTransform gridToCS, final RenderingHints hints) throws InvalidGridGeometryException, CannotReprojectException { if (sources.length == 0) { return; } if (coordinateSystem == null) { if (gridToCS==null && sources.length==1) { return; } coordinateSystem = getCoordinateSystem(sources[MASTER_SOURCE_INDEX]); } if (gridToCS == null) { gridToCS = sources[MASTER_SOURCE_INDEX].getGridGeometry().getGridToCoordinateSystem2D(); } final int dimension = coordinateSystem.getDimension(); // Usually 2. final GridGeometry geometry = new GridGeometry(null, gridToCS); final GridCoverageProcessor processor = getGridCoverageProcessor(hints); for (int i=0; i *
  • Get the {@link SampleDimension}s for the target images by invoking the * {@link #deriveSampleDimension deriveSampleDimension(...)} method.
  • *
  • Apply the operation using the following pseudo-code: *
         * {@link JAI#createNS JAI.createNS}({@link #descriptor}.getName(), parameters, hints)
         * 
  • * * * @param sources The source coverages. * @param parameters Parameters, rendering hints and coordinate system to use. * @return The result as a grid coverage. * * @see #doOperation * @see #deriveSampleDimension * @see JAI#createNS */ protected GridCoverage deriveGridCoverage(final GridCoverage[] sources, final Parameters parameters) { GridCoverage source = sources[MASTER_SOURCE_INDEX]; /* * Get the target SampleDimensions. If they are identical to the SampleDimensions of * one of the source GridCoverage, then this GridCoverage will be used at the master * source. It will affect the target GridCoverage's name and the visible band. Then, * a new color model will be constructed from the new SampleDimensions, taking in * account the visible band. */ final SampleDimension[][] list = new SampleDimension[sources.length][]; for (int i=0; i= sampleDims.length) { visibleBand = 0; } final ColorModel colors; colors = sampleDims[visibleBand].getColorModel(visibleBand, sampleDims.length); layout = layout.setColorModel(colors); } } if (layout != null) { if (hints == null) { hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout); } else { hints.put(JAI.KEY_IMAGE_LAYOUT, layout); } } if (parameters.hints != null) { if (hints != null) { hints.add(parameters.hints); // May overwrite the image layout we just set. } else { hints = parameters.hints; } } /* * Performs the operation using JAI and construct the new grid coverage. * Uses the coordinate system from the main source coverage in order to * preserve the extra dimensions (if any). The first two dimensions should * be equal to the coordinate system set in the 'parameters' block. */ final String name = deriveName(source, parameters); final CoordinateSystem cs = source.getCoordinateSystem(); final MathTransform toCS = source.getGridGeometry().getGridToCoordinateSystem(); final RenderedImage data = createRenderedImage(parameters.parameters, hints); assert parameters.coordinateSystem.equals(CTSUtilities.getSubCoordinateSystem(cs,0,2)) : cs; return new GridCoverage(name, // The grid coverage name data, // The underlying data cs, // The coordinate system (may not be 2D). toCS, // The grid transform (may not be 2D). sampleDims, // The sample dimensions sources, // The source grid coverages. null); // Properties } /** * Returns the index of the quantitative category, providing that there * is one and only one quantitative category. If categories * contains 0, 2 or more quantative category, then this method returns * -1. */ private static int getQuantitative(final Category[] categories) { int index = -1; for (int i=0; i= 0) { return -1; } index = i; } } return index; } /** * Derive the {@link SampleDimension}s for the destination image. The * default implementation iterate among all bands and invokes the * {@link #deriveCategory deriveCategory} and {@link #deriveUnit deriveUnit} * methods for each individual band. * * @param bandLists {@link SampleDimension}s for each band in each source * GridCoverages. For a band (or "sample dimension") * band in a source coverage source, the * corresponding SampleDimension is * * bandLists[source][band]. * * @param parameters Parameters, rendering hints and coordinate system to use. * @return The sample dimensions for each band in the destination image. The * length of this array must matches the number of bands in the * destination image. If the SampleDimensions are unknow, * then this method may returns null. * * @see #deriveCategory * @see #deriveUnit */ protected SampleDimension[] deriveSampleDimension(final SampleDimension[][] bandLists, final Parameters parameters) { /* * Compute the number of bands. Sources with only 1 band are treated as * a special case: their unique band is applied to every band in other * sources. If sources don't have the same number of bands, then this * method returns null since we don't know how to handle * those cases. */ int numBands = 1; for (int i=0; i= 0) { SampleDimension sampleDim = null; Category[] categoryArray = null; int indexOfQuantitative = 0; assert MASTER_SOURCE_INDEX == 0; // See comment below. for (int i=bandLists.length; --i>=0;) { /* * Iterate among all sources (i) for the current band. We iterate * sources in reverse order because the master source MUST be the * last one iterated, in order to have proper value for variables * 'sampleDim', 'categoryArray' and 'indexOfQuantitative' after * the loop. */ final SampleDimension[] allBands = bandLists[i]; sampleDim = allBands[allBands.length==1 ? 0 : numBands]; categoryArray = (Category[]) sampleDim.getCategories().toArray(); indexOfQuantitative = getQuantitative(categoryArray); if (indexOfQuantitative < 0) { return null; } unitXS [i] = sampleDim.getUnits(); categoryXS[i] = categoryArray[indexOfQuantitative]; } final Category oldCategory = categoryArray[indexOfQuantitative]; final Unit oldUnit = sampleDim.getUnits(); final Category newCategory = deriveCategory(categoryXS, parameters); final Unit newUnit = deriveUnit(unitXS, parameters); if (newCategory == null) { return null; } if (!oldCategory.equals(newCategory) || !Utilities.equals(oldUnit, newUnit)) { categoryArray[indexOfQuantitative] = newCategory; result[numBands] = new SampleDimension(categoryArray, newUnit); } else { // Reuse the category list from the master source. result[numBands] = sampleDim; } } return result; } /** * Derive the quantitative category for a {@linkplain SampleDimension sample dimension} * in the destination coverage. This method is invoked automatically by the * {@link #deriveSampleDimension deriveSampleDimension} method for each band in the * destination image. Subclasses should override this method in order to compute the * destination {@link Category} from the source categories. For example, the * "add" operation may implements this method as below: * *
         * NumberRange r0 = categories[0].getRange();
         * NumberRange r1 = categories[0].getRange();
         * double min = r0.getMinimum() + r1.getMinimum();
         * double min = r0.getMaximum() + r1.getMaximum();
         * NumberRange newRange = new NumberRange(min, max);
         * return new Category("My category", null, r0, newRange);
         * 
    * * @param categories The quantitative categories from every sources. For unary operations * like "GradientMagnitude", this array has a length of 1. For binary * operations like "add" and "multiply", this array has a length of 2. * @param parameters Parameters, rendering hints and coordinate system to use. * @return The quantative category to use in the destination image, * or null if unknow. */ protected Category deriveCategory(final Category[] categories, final Parameters parameters) { return null; } /** * Derive the unit of data for a {@linkplain SampleDimension sample dimension} in the * destination coverage. This method is invoked automatically by the * {@link #deriveSampleDimension deriveSampleDimension} method for each band in the * destination image. Subclasses should override this method in order to compute the * destination units from the source units. For example, the "multiply" * operation may implement this method as below: * *
         * if (units[0]!=null && units[1]!=null) {
         *     return units[0].{@link Unit#multiply(Unit) multiply}(units[1]);
         * } else {
         *     return super.deriveUnit(units, cs, parameters);
         * }
         * 
    * * @param units The units from every sources. For unary operations like * "GradientMagnitude", this array has a length of 1. * For binary operations like "add" and "multiply", this array has a length of 2. * @param parameters Parameters, rendering hints and coordinate system to use. * @return The unit of data in the destination image, or null if unknow. */ protected Unit deriveUnit(final Unit[] units, final Parameters parameters) { return null; } /** * Returns a name for the target {@linkplain GridCoverage grid coverage} based on the given * source. The default implementation returns the operation name followed by the source name * between parenthesis. * * @param source The source grid coverage. * @param parameters Parameters, rendering hints and coordinate system to use. * @return A name for the target grid coverage. */ protected String deriveName(final GridCoverage source, final Parameters parameters) { return getName()+'('+source.getName(null)+')'; } /** * Apply the JAI operation. The operation name can be fetch from {@link #descriptor}. * The JAI instance to use can be fetch from {@link #getJAI}. The default implementation * invokes {@link JAI#createNS JAI.createNS}. Subclasses may overrides 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. */ protected RenderedImage createRenderedImage(final ParameterBlockJAI parameters, final RenderingHints hints) { return getJAI(hints).createNS(descriptor.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 null if none. * @return The JAI instance to use (never null). */ protected 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(); } /** * Compares the specified object with this operation for equality. */ 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.descriptor, that.descriptor); } return false; } /** * A block of parameters for a {@link GridCoverage} processed by a {@link OperationJAI}. * This parameter is given to the following methods: * *
      *
    • {@link OperationJAI#deriveSampleDimension}
    • *
    • {@link OperationJAI#deriveCategory}
    • *
    • {@link OperationJAI#deriveUnit}
    • *
    * * @version $Id$ * @author Martin Desruisseaux */ protected static final class Parameters { /** * The coordinate system for the first 2 dimensions of all sources and the destination * {@link GridCoverage}. Sources coverages will be projected in this coordinate system * as needed. Extra dimensions after the first 2 will be ignored. */ public final CoordinateSystem coordinateSystem; /** * The "grid to coordinate system" transform for the first 2 dimensions, * which is common to all source grid coverages. */ public final MathTransform2D gridToCoordinateSystem; /** * 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 createNS call * will be fetch from the {@link Hints#JAI_INSTANCE} key. */ public final RenderingHints hints; /** * The range, colors and units of the main quantitative {@link Category} to be created. * If non-null, then this array length matches the number of sources. */ final RangeSpecifier[] rangeSpecifiers; /** * Construct a new parameter block with the specified values. */ Parameters(final CoordinateSystem coordinateSystem, final MathTransform2D gridToCoordinateSystem, final ParameterBlockJAI parameters, final RenderingHints hints, final RangeSpecifier[] rangeSpecifiers) { this.coordinateSystem = coordinateSystem; this.gridToCoordinateSystem = gridToCoordinateSystem; this.parameters = parameters; this.hints = hints; this.rangeSpecifiers = rangeSpecifiers; } /** * Returns the range specifier for the first source, or null if none. */ final RangeSpecifier getRangeSpecifier() { return (rangeSpecifiers!=null && rangeSpecifiers.length!=0) ? rangeSpecifiers[0] : null; } /** * Returns the first source image, or null if none. */ final RenderedImage getSource() { final int n = parameters.getNumSources(); for (int i=0; i