/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2002-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.operation;
import javax.media.jai.Interpolation;
import javax.media.jai.operator.WarpDescriptor; // For javadoc
import javax.media.jai.operator.AffineDescriptor; // For javadoc
import org.opengis.geometry.Envelope;
import org.opengis.coverage.Coverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.processing.Operation2D;
import org.geotools.coverage.processing.CannotReprojectException;
import org.geotools.factory.Hints;
import org.geotools.referencing.CRS;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.metadata.iso.citation.Citations;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.parameter.DefaultParameterDescriptorGroup;
import org.geotools.util.logging.Logging;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.resources.coverage.CoverageUtilities;
/**
* Resample a grid coverage using a different grid geometry. This operation provides the following
* functionality:
* <p>
* <UL>
* <LI><strong>Resampling</strong><br>
* The grid coverage can be resampled at a different cell resolution. Some implementations
* may be able to do resampling efficiently at any resolution. Also a non-rectilinear grid
* coverage can be accessed as rectilinear grid coverage with this operation.</LI>
* <LI><strong>Reprojecting</strong><br>
* The new grid geometry can have a different coordinate reference system than the underlying
* grid geometry. For example, a grid coverage can be reprojected from a geodetic coordinate
* reference system to Universal Transverse Mercator CRS.</LI>
* <LI><strong>Subsetting</strong><br>
* A subset of a grid can be viewed as a separate coverage by using this operation with a
* grid geometry which as the same geoferencing and a region. Grid range in the grid geometry
* defines the region to subset in the grid coverage.</LI>
* </UL>
* <p>
* <strong>Geotools extension:</strong><br>
* The {@code "Resample"} operation use the default
* {@link org.opengis.referencing.operation.CoordinateOperationFactory} for creating a
* transformation from the source to the destination coordinate reference systems.
* If a custom factory is desired, it may be supplied as a rendering hint with the
* {@link org.geotools.factory.Hints#COORDINATE_OPERATION_FACTORY} key. Rendering
* hints can be supplied to {@link org.geotools.coverage.processing.DefaultProcessor}
* at construction time.
*
* <P><STRONG>Name:</STRONG> <CODE>"Resample"</CODE><BR>
* <STRONG>JAI operator:</STRONG> <CODE>"{@linkplain AffineDescriptor Affine}"</CODE>
* or <CODE>"{@linkplain WarpDescriptor Warp}"</CODE><BR>
* <STRONG>Parameters:</STRONG></P>
* <table border='3' cellpadding='6' bgcolor='F4F8FF'>
* <tr bgcolor='#B9DCFF'>
* <th>Name</th>
* <th>Class</th>
* <th>Default value</th>
* <th>Minimum value</th>
* <th>Maximum value</th>
* </tr>
* <tr>
* <td>{@code "Source"}</td>
* <td>{@link org.geotools.coverage.grid.GridCoverage2D}</td>
* <td align="center">N/A</td>
* <td align="center">N/A</td>
* <td align="center">N/A</td>
* </tr>
* <tr>
* <td>{@code "InterpolationType"}</td>
* <td>{@link java.lang.CharSequence}</td>
* <td>"NearestNieghbor"</td>
* <td align="center">N/A</td>
* <td align="center">N/A</td>
* </tr>
* <tr>
* <td>{@code "CoordinateReferenceSystem"}</td>
* <td>{@link org.opengis.referencing.crs.CoordinateReferenceSystem}</td>
* <td>Same as source grid coverage</td>
* <td align="center">N/A</td>
* <td align="center">N/A</td>
* </tr>
* <tr>
* <td>{@code "GridGeometry"}</td>
* <td>{@link org.opengis.coverage.grid.GridGeometry}</td>
* <td>(automatic)</td>
* <td align="center">N/A</td>
* <td align="center">N/A</td>
* </tr>
* </table>
*
* @since 2.2
* @source $URL$
* @version $Id$
* @author Martin Desruisseaux (IRD)
*
* @see org.geotools.coverage.processing.Operations#resample
* @see WarpDescriptor
*/
public class Resample extends Operation2D {
/**
* Serial number for interoperability with different versions.
*/
private static final long serialVersionUID = -2022393087647420577L;
/**
* The parameter descriptor for the interpolation type.
*/
public static final ParameterDescriptor INTERPOLATION_TYPE =
new DefaultParameterDescriptor(Citations.OGC, "InterpolationType",
Object.class, // Value class (mandatory)
null, // Array of valid values
"NearestNeighbor", // Default value
null, // Minimal value
null, // Maximal value
null, // Unit of measure
false); // Parameter is optional
/**
* The parameter descriptor for the coordinate reference system.
*/
public static final ParameterDescriptor COORDINATE_REFERENCE_SYSTEM =
new DefaultParameterDescriptor(Citations.OGC, "CoordinateReferenceSystem",
CoordinateReferenceSystem.class, // Value class (mandatory)
null, // Array of valid values
null, // Default value
null, // Minimal value
null, // Maximal value
null, // Unit of measure
false); // Parameter is optional
/**
* The parameter descriptor for the grid geometry.
*/
public static final ParameterDescriptor GRID_GEOMETRY =
new DefaultParameterDescriptor(Citations.OGC, "GridGeometry",
GridGeometry.class, // Value class (mandatory)
null, // Array of valid values
null, // Default value
null, // Minimal value
null, // Maximal value
null, // Unit of measure
false); // Parameter is optional
/**
* Constructs a {@code "Resample"} operation.
*/
public Resample() {
super(new DefaultParameterDescriptorGroup(Citations.OGC, "Resample",
new ParameterDescriptor[] {
SOURCE_0,
INTERPOLATION_TYPE,
COORDINATE_REFERENCE_SYSTEM,
GRID_GEOMETRY
}));
}
/**
* Resamples a grid coverage. This method is invoked by
* {@link org.geotools.coverage.processing.DefaultProcessor}
* for the {@code "Resample"} operation.
*/
public Coverage doOperation(final ParameterValueGroup parameters, final Hints hints) {
final GridCoverage2D source = (GridCoverage2D) parameters.parameter("Source").getValue();
final Interpolation interpolation = ImageUtilities.toInterpolation(
parameters.parameter("InterpolationType").getValue());
CoordinateReferenceSystem targetCRS = (CoordinateReferenceSystem)
parameters.parameter("CoordinateReferenceSystem").getValue();
if (targetCRS == null) {
targetCRS = source.getCoordinateReferenceSystem();
}
final GridGeometry2D targetGG = GridGeometry2D.wrap(
(GridGeometry) parameters.parameter("GridGeometry").getValue());
final GridCoverage2D target;
try {
target = Resampler2D.reproject(source, targetCRS, targetGG, interpolation,
(hints instanceof Hints) ? hints : new Hints(hints));
} catch (FactoryException exception) {
throw new CannotReprojectException(Errors.format(
ErrorKeys.CANT_REPROJECT_$1, source.getName()), exception);
} catch (TransformException exception) {
throw new CannotReprojectException(Errors.format(
ErrorKeys.CANT_REPROJECT_$1, source.getName()), exception);
}
return target;
}
/**
* Computes a grid geometry from a source coverage and a target envelope. This is a convenience
* method for computing the {@link #GRID_GEOMETRY} argument of a {@code "resample"} operation
* from an envelope. The target envelope may contains a different coordinate reference system,
* in which case a reprojection will be performed.
*
* @param source The source coverage.
* @param target The target envelope, including a possibly different coordinate reference system.
* @return A grid geometry inferred from the target envelope.
* @throws TransformException If a transformation was required and failed.
*
* @since 2.5
*/
public static GridGeometry computeGridGeometry(final GridCoverage source, final Envelope target)
throws TransformException
{
final CoordinateReferenceSystem targetCRS = target.getCoordinateReferenceSystem();
final CoordinateReferenceSystem sourceCRS = source.getCoordinateReferenceSystem();
final CoordinateReferenceSystem reducedCRS;
if (target.getDimension() == 2 && sourceCRS.getCoordinateSystem().getDimension() != 2) {
reducedCRS = CoverageUtilities.getCRS2D(source);
} else {
reducedCRS = sourceCRS;
}
GridGeometry gridGeometry = source.getGridGeometry();
if (targetCRS == null || CRS.equalsIgnoreMetadata(reducedCRS, targetCRS)) {
/*
* Same CRS (or unknown target CRS, which we treat as same), so we will keep the same
* "gridToCRS" transform. Basically the result will be the same as if we did a crop,
* except that we need to take in account a change from nD to 2D.
*/
final MathTransform gridToCRS;
if (reducedCRS == sourceCRS) {
gridToCRS = gridGeometry.getGridToCRS();
} else {
gridToCRS = GridGeometry2D.wrap(gridGeometry).getGridToCRS2D();
}
gridGeometry = new GridGeometry2D(PixelInCell.CELL_CENTER, gridToCRS, target, null);
} else {
/*
* Different CRS. We need to infer an image size, which may be the same than the
* original size or something smaller if the envelope is a subarea. We process by
* transforming the target envelope to the source CRS and compute a new grid geometry
* with that envelope. The grid range of that grid geometry is the new image size.
* Note that failure to transform the envelope is non-fatal (we will assume that the
* target image should have the same size). Then create again a new grid geometry,
* this time with the target envelope.
*/
GridEnvelope gridRange;
try {
final GeneralEnvelope transformed;
transformed = CRS.transform(CRS.getCoordinateOperationFactory(true)
.createOperation(targetCRS, reducedCRS), target);
final Envelope reduced;
final MathTransform gridToCRS;
if (reducedCRS == sourceCRS) {
reduced = source.getEnvelope();
gridToCRS = gridGeometry.getGridToCRS();
} else {
reduced = CoverageUtilities.getEnvelope2D(source);
gridToCRS = GridGeometry2D.wrap(gridGeometry).getGridToCRS2D();
}
transformed.intersect(reduced);
gridGeometry = new GridGeometry2D(PixelInCell.CELL_CENTER, gridToCRS, transformed, null);
} catch (FactoryException exception) {
recoverableException("resample", exception);
} catch (TransformException exception) {
recoverableException("resample", exception);
// Will use the grid range from the original geometry,
// which will result in keeping the same image size.
}
gridRange = gridGeometry.getGridRange();
gridGeometry = new GridGeometry2D(gridRange, target);
}
return gridGeometry;
}
/**
* Invoked when an error occured but the application can fallback on a reasonable default.
*
* @param method The method where the error occured.
* @param exception The error.
*/
private static void recoverableException(final String method, final Exception exception) {
Logging.recoverableException(Resample.class, method, exception);
}
}