//// ////
////////////////////////////////////////////////////////////////////////////////////////
final CoordinateOperationFactory factory =
ReferencingFactoryFinder.getCoordinateOperationFactory(hints);
final MathTransformFactory mtFactory;
if (factory instanceof AbstractCoordinateOperationFactory) {
mtFactory = ((AbstractCoordinateOperationFactory) factory).getMathTransformFactory();
} else {
mtFactory = ReferencingFactoryFinder.getMathTransformFactory(hints);
}
/*
* Computes the INVERSE of the math transform from [Source Grid] to [Target Grid].
* The transform will be computed using the following path:
*
* Target Grid --> Target CRS --> Source CRS --> Source Grid
* ^ ^ ^
* step 1 step 2 step 3
*
* If source and target CRS are equal, a shorter path is used. This special
* case is needed because 'sourceCRS' and 'targetCRS' may be null.
*
* Target Grid --> Common CRS --> Source Grid
* ^ ^
* step 1 step 3
*/
final MathTransform step1, step2, step3, allSteps, allSteps2D;
if (CRS.equalsIgnoreMetadata(sourceCRS, targetCRS)) {
/*
* Note: targetGG should not be null, otherwise 'existingCoverage(...)' should
* have already detected that this resample is not doing anything.
*/
if (!targetGG.isDefined(GridGeometry2D.GRID_TO_CRS_BITMASK)) {
step1 = sourceGG.getGridToCRS(CORNER); // Really sourceGG, not targetGG
step2 = IdentityTransform.create(step1.getTargetDimensions());
step3 = step1.inverse();
allSteps = IdentityTransform.create(step1.getSourceDimensions());
targetGG = new GridGeometry2D(targetGG.getGridRange(), step1, targetCRS);
} else {
step1 = targetGG.getGridToCRS(CORNER);
step2 = IdentityTransform.create(step1.getTargetDimensions());
step3 = sourceGG.getGridToCRS(CORNER).inverse();
allSteps = mtFactory.createConcatenatedTransform(step1, step3);
if (!targetGG.isDefined(GridGeometry2D.GRID_RANGE_BITMASK)) {
/*
* If the target grid range was not explicitly specified, a grid range will be
* automatically computed in such a way that it will maps to the same envelope
* (at least approximatively).
*/
Envelope gridRange;
gridRange = toEnvelope(sourceGG.getGridRange());
gridRange = CRS.transform(allSteps.inverse(), gridRange);
targetGG = new GridGeometry2D(new GeneralGridEnvelope(gridRange,PixelInCell.CELL_CORNER), targetGG.getGridToCRS(PixelInCell.CELL_CENTER), targetCRS);
}
}
} else {
if (sourceCRS == null) {
throw new CannotReprojectException(Errors.format(ErrorKeys.UNSPECIFIED_CRS));
}
final Envelope sourceEnvelope;
final GeneralEnvelope targetEnvelope;
final CoordinateOperation operation = factory.createOperation(sourceCRS, targetCRS);
final boolean force2D = (sourceCRS != compatibleSourceCRS);
step2 = factory.createOperation(targetCRS, compatibleSourceCRS).getMathTransform();
step3 = (force2D ? sourceGG.getGridToCRS2D(CORNER) : sourceGG.getGridToCRS(CORNER)).inverse();
sourceEnvelope = sourceCoverage.getEnvelope(); // Don't force this one to 2D.
targetEnvelope = CRS.transform(operation, sourceEnvelope);
targetEnvelope.setCoordinateReferenceSystem(targetCRS);
// 'targetCRS' may be different than the one set by CRS.transform(...).
/*
* If the target GridGeometry is incomplete, provides default
* values for the missing fields. Three cases may occurs:
*
* - User provided no GridGeometry at all. Then, constructs an image of the same size
* than the source image and set an envelope big enough to contains the projected
* coordinates. The transform will derive from the grid range and the envelope.
*
* - User provided only a grid range. Then, set an envelope big enough to contains
* the projected coordinates. The transform will derive from the grid range and
* the envelope.
*
* - User provided only a "grid to CRS" transform. Then, transform the projected
* envelope to "grid units" using the specified transform and create a grid range
* big enough to hold the result.
*/
if (targetGG == null) {
final GridEnvelope targetGR;
targetGR = force2D ? new GridEnvelope2D(sourceGG.getGridRange2D()) : sourceGG.getGridRange();
targetGG = new GridGeometry2D(targetGR, targetEnvelope);
step1 = targetGG.getGridToCRS(CORNER);
} else if (!targetGG.isDefined(GridGeometry2D.GRID_TO_CRS_BITMASK)) {
targetGG = new GridGeometry2D(targetGG.getGridRange(), targetEnvelope);
step1 = targetGG.getGridToCRS(CORNER);
} else {
step1 = targetGG.getGridToCRS(CORNER);
if (!targetGG.isDefined(GridGeometry2D.GRID_RANGE_BITMASK)) {
GeneralEnvelope gridRange = CRS.transform(step1.inverse(), targetEnvelope);
// According OpenGIS specification, GridGeometry maps pixel's center.
targetGG = new GridGeometry2D(new GeneralGridEnvelope(gridRange, PixelInCell.CELL_CENTER), step1, targetCRS);
}
}
/*
* Computes the final transform.
*/
allSteps = mtFactory.createConcatenatedTransform(
mtFactory.createConcatenatedTransform(step1, step2), step3);
}
allSteps2D = toMathTransform2D(allSteps, mtFactory, targetGG);
if (!(allSteps2D instanceof MathTransform2D)) {
// Should not happen with Geotools implementations. May happen
// with some external implementations, but should stay unusual.
throw new TransformException(Errors.format(ErrorKeys.NO_TRANSFORM2D_AVAILABLE));
}
////////////////////////////////////////////////////////////////////////////////////////
//// ////
//// STEP 1: Extracts needed informations from the parameters ////
//// STEP 2: Creates the "target to source" MathTransform ////
//// =======>> STEP 3: Computes the target image layout <<====== ////
//// STEP 4: Applies the JAI operation ("Affine", "Warp", etc) ////
//// ////
////////////////////////////////////////////////////////////////////////////////////////
final RenderingHints targetHints = processingView.getRenderingHints(sourceImage);
if (hints != null) {
targetHints.add(hints);
}
ImageLayout layout = (ImageLayout) targetHints.get(JAI.KEY_IMAGE_LAYOUT);
if (layout != null) {
layout = (ImageLayout) layout.clone();
} else {
layout = new ImageLayout();
// Do not inherit the color model and sample model from the 'sourceImage';
// Let the operation decide itself. This is necessary in case we change the
// source, as we do if we choose the "Mosaic" operation.
}
final Rectangle sourceBB = sourceGG.getGridRange2D();
final Rectangle targetBB = targetGG.getGridRange2D();
if (isBoundsUndefined(layout, false)) {
layout.setMinX (targetBB.x);
layout.setMinY (targetBB.y);
layout.setWidth (targetBB.width);
layout.setHeight(targetBB.height);
}
if (isBoundsUndefined(layout, true)) {
Dimension size = new Dimension(layout.getWidth (sourceImage),
layout.getHeight(sourceImage));
size = ImageUtilities.toTileSize(size);
layout.setTileGridXOffset(layout.getMinX(sourceImage));
layout.setTileGridYOffset(layout.getMinY(sourceImage));
layout.setTileWidth (size.width);
layout.setTileHeight(size.height);
}
/*
* Creates the background values array.
*/
final double[] background = backgroundValues != null ? backgroundValues : CoverageUtilities.getBackgroundValues(sourceCoverage);
/*
* We need to correctly manage the Hints to control the replacement of IndexColorModel.
* It is worth to point out that setting the JAI.KEY_REPLACE_INDEX_COLOR_MODEL hint to
* Boolean.TRUE is not enough to force the operators to do an expansion. If we explicitly
* provide an ImageLayout built with the source image where the CM and the SM are valid.
* those will be employed overriding a the possibility to expand the color model.
*/
if (ViewType.PHOTOGRAPHIC.equals(processingView)) {
layout.unsetValid(ImageLayout.COLOR_MODEL_MASK | ImageLayout.SAMPLE_MODEL_MASK);
}
targetHints.put(JAI.KEY_IMAGE_LAYOUT, layout);
////////////////////////////////////////////////////////////////////////////////////////
//// ////
//// STEP 1: Extracts needed informations from the parameters ////
//// STEP 2: Creates the "target to source" MathTransform ////
//// STEP 3: Computes the target image layout ////
//// =======>> STEP 4: Applies the JAI operation ("Affine", "Warp", etc) <<====== ////
//// ////
////////////////////////////////////////////////////////////////////////////////////////
/*
* If the user requests a new grid geometry with the same coordinate reference system,
* and if the grid geometry is equivalents to a simple extraction of a sub-area, then
* delegates the work to a "Crop" operation.
*/
final String operation;
final ParameterBlock paramBlk = new ParameterBlock().addSource(sourceImage);
final Map<String, Object> imageProperties = new HashMap<String, Object>();
Warp warp = null;
if (allSteps.isIdentity() || (allSteps instanceof AffineTransform &&
XAffineTransform.isIdentity((AffineTransform) allSteps, EPS)))
{
/*
* Since there is no interpolation to perform, use the native view (which may be
* packed or geophysics - it is just the view which is closest to original data).
*/
sourceCoverage = sourceCoverage.view(ViewType.NATIVE);
sourceImage = PlanarImage.wrapRenderedImage(sourceCoverage.getRenderedImage());
paramBlk.removeSources();
paramBlk.addSource(sourceImage);
if (targetBB.equals(sourceBB)) {
/*
* Optimization in case we have nothing to do, not even a crop. Reverts to the
* original coverage BEFORE to creates Resampler2D. Note that while there is
* nothing to do, the target CRS is not identical to the source CRS (so we need
* to create a new coverage) otherwise this condition would have been detected
* sooner in this method.
*/
sourceCoverage = sourceCoverage.view(finalView);
sourceImage = PlanarImage.wrapRenderedImage(sourceCoverage.getRenderedImage());
return create(sourceCoverage, sourceImage, targetGG, ViewType.SAME, null, null, hints);
}
if (sourceBB.contains(targetBB)) {
operation = "Crop";
paramBlk.add(Float.valueOf(targetBB.x))
.add(Float.valueOf(targetBB.y))
.add(Float.valueOf(targetBB.width))
.add(Float.valueOf(targetBB.height));
} else {
operation = "Mosaic";
paramBlk.add(MosaicDescriptor.MOSAIC_TYPE_OVERLAY)
.add(null).add(null).add(null).add(background);
}
} else {
/*
* Special case for the affine transform. Try to use the JAI "Affine" operation
* instead of the more general "Warp" one. JAI provides native acceleration for
* the affine operation.
*
* NOTE 1: There is no need to check for "Scale" and "Translate" as special cases
* of "Affine" since JAI already does this check for us.
*
* NOTE 2: "Affine", "Scale", "Translate", "Rotate" and similar operations ignore
* the 'xmin', 'ymin', 'width' and 'height' image layout. Consequently, we
* can't use this operation if the user provided explicitly a grid range.
*
* NOTE 3: If the user didn't specified any grid geometry, then a yet cheaper approach
* is to just update the 'gridToCRS' value. We returns a grid coverage wrapping
* the SOURCE image with the updated grid geometry.
*/
if ((automaticGR || targetBB.equals(sourceBB)) && allSteps instanceof AffineTransform) {
if (automaticGG) {
// Cheapest approach: just update 'gridToCRS'.
MathTransform mtr;
mtr = sourceGG.getGridToCRS(CORNER);
mtr = mtFactory.createConcatenatedTransform(mtr, step2.inverse());
targetGG = new GridGeometry2D(sourceGG.getGridRange(), mtr, targetCRS);
/*
* Note: do NOT use the "GridGeometry2D(sourceGridRange, targetEnvelope)"
* constructor in the above line. We must give a MathTransform argument to
* the constructor, not an Envelope, because the later infer a MathTransform