/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2007-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.io;
import it.geosolutions.imageio.imageioimpl.imagereadmt.DefaultCloneableImageReadParam;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.measure.unit.Unit;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.data.DataSourceException;
import org.geotools.factory.Hints;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotools.referencing.operation.transform.IdentityTransform;
import org.geotools.referencing.operation.transform.ProjectiveTransform;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.geometry.XRectangle2D;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterValue;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
/**
* A class to handle coverage requests to a reader.
*
* @author Daniele Romagnoli, GeoSolutions
* @author Simone Giannecchini, GeoSolutions
*/
class BaseCoverageRequest {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging
.getLogger("org.geotools.coverageio");
enum ReadType {
DIRECT_READ, JAI_IMAGEREAD, UNSPECIFIED;
public static ReadType getDefault() {
return DIRECT_READ;
}
};
private ReadType readType = ReadType.UNSPECIFIED;
// ////////////////////////////////////////////////////////////////////////
//
// Base coverage properties
//
// ////////////////////////////////////////////////////////////////////////
/** The base envelope read from file */
private GeneralEnvelope coverageEnvelope = null;
/** The base envelope 2D */
private Envelope2D baseEnvelope2D;
/** WGS84 envelope 2D for this coverage */
private Envelope2D wgs84BaseEnvelope2D;
/** The CRS for the coverage */
private CoordinateReferenceSystem coverageCRS = null;
/** The CRS related to the base envelope 2D */
private CoordinateReferenceSystem spatialReferenceSystem2D;
/** The Coverage name */
private String coverageName;
/** The base grid range for the coverage */
private GeneralGridEnvelope baseGridRange;
private double[] highestRes;
private MathTransform raster2Model;
private double[] scaleAndOffset;
private double[] validRange;
private double[] noDataValues;
private String longName;
private Unit unit;
// ////////////////////////////////////////////////////////////////////////
//
// Request specific properties
//
// ////////////////////////////////////////////////////////////////////////
/** The envelope requested */
private GeneralEnvelope requestedEnvelope2D;
/**
* The adjusted requested envelope. It is the envelope obtained by properly
* intersecting the requested envelope with the base envelope.
*/
private GeneralEnvelope adjustedRequestedEnvelope2D;
/** The desired overview Policy for this request */
private OverviewPolicy overviewPolicy;
/** The region where to fit the requested envelope */
private Rectangle requestedRasterDimension;
/** */
private MathTransform2D requestedGridToWorld2D;
private Hints hints;
/**
* Specify if a JAI ImageRead operation should use multithreading or not.
* Note that multithreading is supported using a special JAI ImageReadMT
* operation
*/
private boolean useMultithreading = false;
/**
* The imageRead parameters involved in the coverage request (source region,
* subsampling factors) which will be used by a coverageResponse to read
* data.
*/
private ImageReadParam imageReadParam = null;
/** The source */
private Rectangle sourceRasterRegion;
/**
* If set to {@code true} a transformation is requested to obtain the
* desired data. This usually happens when the requested envelope will be
* adjusted with intersection/crop of the base envelope.
*/
private boolean needTransformation;
/**
* Set to {@code true} if this request will produce an empty result, and the
* coverageResponse will produce a {@code null} coverage.
*/
private boolean emptyRequest;
/** The input data */
private File input;
/**
* Set to {@code true} if the read operation needed to request data is a JAI
* Image Read operation. Set to {@code false} if the read operation is a
* direct {@code ImageReader.read(...)} call.
*/
private boolean useJAI;
/** An optional layout to be adopted */
private ImageLayout layout = null;
private int imageIndex;
/**
* Build a new {@code BaseCoverageRequest} given a set of input parameters.
*
* @param params
* The {@code GeneralParameterValue}s to initialize this
* request
* @param baseGridCoverage2DReader
*/
public BaseCoverageRequest(GeneralParameterValue[] params,
BaseGridCoverage2DReader baseGridCoverage2DReader) {
// //
//
// Parsing parameters
//
// //
if (params != null) {
for (GeneralParameterValue gParam : params) {
final ParameterValue<?> param = (ParameterValue<?>) gParam;
final ReferenceIdentifier name = param.getDescriptor()
.getName();
extractParameter(param, name);
}
}
setBaseParameters(baseGridCoverage2DReader);
}
/**
* Set the main parameters of this coverage request, getting basic
* information from the reader.
*
* @param reader
* a {@link BaseGridCoverage2DReader} from where to get basic
* coverage properties as well as basic parameters to be used
* by the incoming read operations.
*/
private void setBaseParameters(final BaseGridCoverage2DReader reader) {
this.input = reader.getInputFile();
this.coverageEnvelope = reader.getCoverageEnvelope().clone();
this.baseGridRange = reader.getCoverageGridRange();
this.coverageCRS = reader.getCoverageCRS();
this.coverageName = reader.getCoverageName();
this.raster2Model = reader.getRaster2Model();
this.highestRes = reader.getHighestRes();
this.hints = reader.getHints().clone();
this.imageIndex = reader.getImageIndex();
this.scaleAndOffset = reader.getScaleAndOffset();
this.validRange = reader.getValidRange();
this.longName = reader.getLongName();
this.noDataValues = reader.getNoDataValues();
this.unit = reader.getUnit();
if (this.layout != null) {
this.hints
.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, this.layout));
}
}
/**
* Set proper fields from the specified input parameter.
*
* @param param
* the input {@code ParamaterValue} object
* @param name
* the name of the parameter
*/
private void extractParameter(ParameterValue<?> param, Identifier name) {
// //
//
// GridGeometry2D parameter
//
// //
if (name.equals(AbstractGridFormat.READ_GRIDGEOMETRY2D.getName())) {
final GridGeometry2D gg = (GridGeometry2D) param.getValue();
if (gg == null) {
return;
}
requestedGridToWorld2D = gg.getGridToCRS2D();
requestedEnvelope2D = new GeneralEnvelope((Envelope) gg
.getEnvelope2D());
requestedRasterDimension = gg.getGridRange2D().getBounds();
return;
}
// //
//
// Use JAI ImageRead parameter
//
// //
if (name.equals(AbstractGridFormat.USE_JAI_IMAGEREAD.getName())) {
readType = param.booleanValue() ? ReadType.JAI_IMAGEREAD
: ReadType.DIRECT_READ;
return;
}
// //
//
// Use Multithreading parameter
//
// //
if (name.equals(BaseGridFormat.USE_MULTITHREADING.getName())) {
useMultithreading = param.booleanValue();
return;
}
// //
//
// Overview Policy parameter
//
// //
if (name.equals(AbstractGridFormat.OVERVIEW_POLICY.getName())) {
overviewPolicy = (OverviewPolicy) param.getValue();
return;
}
// //
//
// Suggested tile size parameter. It must be specified with
// the syntax: "TileWidth,TileHeight" (without quotes where TileWidth
// and TileHeight are integer values)
//
// //
if (name.equals(BaseGridFormat.SUGGESTED_TILE_SIZE.getName())) {
final String suggestedTileSize = (String) param.getValue();
// Preliminary checks on parameter value
if ((suggestedTileSize != null)
&& (suggestedTileSize.trim().length() > 0)) {
if (suggestedTileSize
.contains(BaseGridFormat.TILE_SIZE_SEPARATOR)) {
final String[] tilesSize = suggestedTileSize
.split(BaseGridFormat.TILE_SIZE_SEPARATOR);
if (tilesSize.length == 2) {
try {
// Getting suggested tile size
final int tileWidth = Integer.parseInt(tilesSize[0]
.trim());
final int tileHeight = Integer
.parseInt(tilesSize[1].trim());
layout = new ImageLayout();
layout.setTileGridXOffset(0).setTileGridYOffset(0)
.setTileHeight(tileHeight).setTileWidth(
tileWidth);
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to parse "
+ "suggested tile size parameter");
}
}
}
}
}
}
}
/**
* Compute this specific request settings all the parameters needed by a
* visiting {@link BaseCoverageResponse} object.
*/
public void prepare() {
try {
// //
//
// Set envelope and source region
//
// //
sourceRasterRegion = new Rectangle();
adjustedRequestedEnvelope2D = evaluateRequestedParams(
requestedEnvelope2D, sourceRasterRegion,
requestedRasterDimension, requestedGridToWorld2D);
// //
//
// Set specific imageIO parameters: type of read operation,
// imageReadParams
//
// //
useJAI = requestUsesJaiImageread();
if (useMultithreading) {
imageReadParam = new DefaultCloneableImageReadParam();
} else {
imageReadParam = new ImageReadParam();
}
if (adjustedRequestedEnvelope2D != null) {
final GeneralEnvelope req = (adjustedRequestedEnvelope2D
.isEmpty()) ? requestedEnvelope2D
: adjustedRequestedEnvelope2D;
setReadParameters(overviewPolicy, imageReadParam, req,
requestedRasterDimension);
}
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
adjustedRequestedEnvelope2D = null;
} catch (TransformException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
adjustedRequestedEnvelope2D = null;
}
if (adjustedRequestedEnvelope2D != null && sourceRasterRegion != null
&& !sourceRasterRegion.isEmpty()) {
imageReadParam.setSourceRegion(sourceRasterRegion);
}
// A transformation is requested in case the requested envelope has been
// adjusted
needTransformation = (adjustedRequestedEnvelope2D != null && !adjustedRequestedEnvelope2D
.isEmpty());
// In case the adjusted requested envelope is null, no intersection
// between requested envelope and base envelope have been found. Hence,
// no valid coverage will be loaded and the request should be considered
// as producing an empty result.
emptyRequest = adjustedRequestedEnvelope2D == null;
}
/**
* Check the type of read operation which will be performed and return
* {@code true} if a JAI imageRead operation need to be performed or
* {@code false} if a simple read operation is needed.
*
* @return {@code true} if the read operation will use a JAI ImageRead
* operation instead of a simple {@code ImageReader.read(...)} call.
*/
private boolean requestUsesJaiImageread() {
// //
//
// First of all check if the ReadType was already set as part the
// request parameters
//
// //
if (readType != ReadType.UNSPECIFIED)
return readType == ReadType.JAI_IMAGEREAD;
// //
//
// Ok, the request did not explicitly set the read type, let's check the
// hints.
//
// //
if (this.hints != null) {
final Object o = this.hints.get(Hints.USE_JAI_IMAGEREAD);
if (o != null) {
return ((Boolean) o);
}
}
// //
//
// Last chance is to use the default read type.
//
// //
readType = ReadType.getDefault();
return readType == ReadType.JAI_IMAGEREAD;
}
/**
* Return a crop region from a specified envelope, leveraging on the grid to
* world transformation.
*
* @param envelope
* the crop envelope
* @return a {@code Rectangle} representing the crop region.
* @throws TransformException
* in case a problem occurs when going back to raster space.
*/
private Rectangle getCropRegion(GeneralEnvelope envelope)
throws TransformException {
final MathTransform gridToWorldTransform = getOriginalGridToWorld(PixelInCell.CELL_CORNER);
final MathTransform worldToGridTransform = gridToWorldTransform
.inverse();
final GeneralEnvelope rasterArea = CRS.transform(worldToGridTransform,
envelope);
final Rectangle2D ordinates = rasterArea.toRectangle2D();
return ordinates.getBounds();
}
/**
* Prepares the read parameters for doing an
* {@link ImageReader#read(int, ImageReadParam)}.
*
* It sets the passed {@link ImageReadParam} in terms of decimation on
* reading using the provided requestedEnvelope and requestedDim to evaluate
* the needed resolution.
*
* @param overviewPolicy
* it can be one of
* {@link Hints#VALUE_OVERVIEW_POLICY_IGNORE},
* {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST},
* {@link Hints#VALUE_OVERVIEW_POLICY_QUALITY} or
* {@link Hints#VALUE_OVERVIEW_POLICY_SPEED}. It specifies
* the policy to compute the overviews level upon request.
* @param readParam
* an instance of {@link ImageReadParam} for setting the
* subsampling factors.
* @param requestedEnvelope
* the {@link GeneralEnvelope} we are requesting.
* @param requestedDim
* the requested dimensions.
* @throws IOException
* @throws TransformException
*/
protected void setReadParameters(OverviewPolicy overviewPolicy,
ImageReadParam readParam, GeneralEnvelope requestedEnvelope,
Rectangle requestedDim) throws IOException, TransformException {
double[] requestedRes = null;
// //
//
// Initialize overview policy
//
// //
if (overviewPolicy == null) {
overviewPolicy = OverviewPolicy.NEAREST;
}
// //
//
// default values for subsampling
//
// //
readParam.setSourceSubsampling(1, 1, 0, 0);
// //
//
// requested to ignore overviews
//
// //
if (overviewPolicy.equals(OverviewPolicy.IGNORE)) {
return;
}
// //
//
// Resolution requested. I am here computing the resolution required
// by the user.
//
// //
if (requestedEnvelope != null) {
final GridToEnvelopeMapper geMapper = new GridToEnvelopeMapper();
geMapper.setEnvelope(requestedEnvelope);
geMapper.setGridRange(new GeneralGridEnvelope(requestedDim));
geMapper.setPixelAnchor(PixelInCell.CELL_CORNER);
AffineTransform transform = geMapper.createAffineTransform();
requestedRes = CoverageUtilities.getResolution(transform);
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE, "requested resolution: ("
+ requestedRes[0] + "," + requestedRes[1] + ")");
}
}
if (requestedRes == null) {
return;
}
// ////////////////////////////////////////////////////////////////////
//
// DECIMATION ON READING
//
// ////////////////////////////////////////////////////////////////////
if ((requestedRes[0] > highestRes[0])
|| (requestedRes[1] > highestRes[1])) {
setDecimationParameters(readParam, requestedRes);
}
}
/**
* Evaluates the requested envelope and builds a new adjusted version of it
* fitting this coverage envelope.
*
* <p>
* While adjusting the requested envelope this methods also compute the
* source region as a rectangle which is suitable for a successive read
* operation with {@link ImageIO} to do crop-on-read.
*
*
* @param requestedEnvelope
* is the envelope we are requested to load.
* @param sourceRegion
* represents the area to load in raster space. This
* parameter cannot be null since it gets filled with
* whatever the crop region is depending on the
* <code>requestedEnvelope</code>.
* @param requestedDim
* is the requested region where to load data of the
* specified envelope.
* @param readGridToWorld
* the Grid to world transformation to be used
* @return the adjusted requested envelope, empty if no requestedEnvelope
* has been specified, {@code null} in case the requested envelope
* does not intersect the coverage envelope or in case the adjusted
* requested envelope is covered by a too small raster region (an
* empty region).
*
* @throws DataSourceException
* in case something bad occurs
*/
private GeneralEnvelope evaluateRequestedParams(
GeneralEnvelope requestedEnvelope, Rectangle sourceRegion,
Rectangle requestedDim, MathTransform2D readGridToWorld)
throws DataSourceException {
GeneralEnvelope adjustedRequestedEnvelope = new GeneralEnvelope(2);
try {
// ////////////////////////////////////////////////////////////////
//
// Check if we have something to load by intersecting the
// requested envelope with the bounds of this data set.
//
// ////////////////////////////////////////////////////////////////
if (requestedEnvelope != null) {
initBaseEnvelope2D();
final GeneralEnvelope requestedEnvelope2D = getRequestedEnvelope2D(requestedEnvelope);
// ////////////////////////////////////////////////////////////
//
// INTERSECT ENVELOPES AND CROP Destination REGION
//
// ////////////////////////////////////////////////////////////
adjustedRequestedEnvelope = getIntersection(
requestedEnvelope2D, requestedDim, readGridToWorld);
if (adjustedRequestedEnvelope == null)
return null;
// /////////////////////////////////////////////////////////////////////
//
// CROP SOURCE REGION
//
// /////////////////////////////////////////////////////////////////////
sourceRegion.setRect(getCropRegion(adjustedRequestedEnvelope));
if (sourceRegion.isEmpty()) {
if (LOGGER.isLoggable(Level.INFO)) {
LOGGER.log(Level.INFO,
"Too small envelope resulting in "
+ "empty cropped raster region");
}
return null;
// TODO: Future versions may define a 1x1 rectangle starting
// from the lower coordinate
}
if (!sourceRegion.intersects(baseGridRange.toRectangle())
|| sourceRegion.isEmpty())
throw new DataSourceException("The crop region is invalid.");
sourceRegion.setRect(sourceRegion.intersection(baseGridRange
.toRectangle()));
if (LOGGER.isLoggable(Level.FINE)) {
StringBuffer sb = new StringBuffer(
"Adjusted Requested Envelope = ").append(
adjustedRequestedEnvelope.toString()).append("\n")
.append("Requested raster dimension = ").append(
requestedDim.toString()).append("\n")
.append("Corresponding raster source region = ")
.append(sourceRegion.toString());
LOGGER.log(Level.FINE, sb.toString());
}
} else {
// don't use the source region. Set an empty one
sourceRegion.setBounds(new Rectangle(0, 0, Integer.MIN_VALUE,
Integer.MIN_VALUE));
}
} catch (TransformException e) {
throw new DataSourceException(
"Unable to create a coverage for this source", e);
} catch (FactoryException e) {
throw new DataSourceException(
"Unable to create a coverage for this source", e);
}
return adjustedRequestedEnvelope;
}
/**
* Return a 2D version of a requestedEnvelope
*
* @param requestedEnvelope
* the {@code GeneralEnvelope} to be returned as 2D.
* @return the 2D requested envelope
* @throws FactoryException
* @throws TransformException
*/
private GeneralEnvelope getRequestedEnvelope2D(
GeneralEnvelope requestedEnvelope) throws FactoryException,
TransformException {
GeneralEnvelope requestedEnvelope2D = null;
final MathTransform transformTo2D;
CoordinateReferenceSystem requestedEnvelopeCRS2D = requestedEnvelope
.getCoordinateReferenceSystem();
// //
//
// Find the transformation to 2D
//
// //
if (requestedEnvelopeCRS2D.getCoordinateSystem().getDimension() != 2) {
transformTo2D = CRS.findMathTransform(requestedEnvelopeCRS2D, CRS
.getHorizontalCRS(requestedEnvelopeCRS2D));
requestedEnvelopeCRS2D = CRS
.getHorizontalCRS(requestedEnvelopeCRS2D);
} else
transformTo2D = IdentityTransform.create(2);
if (!transformTo2D.isIdentity()) {
requestedEnvelope2D = CRS.transform(transformTo2D,
requestedEnvelope);
requestedEnvelope2D
.setCoordinateReferenceSystem(requestedEnvelopeCRS2D);
} else
requestedEnvelope2D = new GeneralEnvelope(requestedEnvelope);
assert requestedEnvelopeCRS2D.getCoordinateSystem().getDimension() == 2;
return requestedEnvelope2D;
}
/**
* Initialize the 2D properties (CRS and Envelope) of this coverage
*
* @throws FactoryException
* @throws TransformException
*/
private void initBaseEnvelope2D() throws FactoryException,
TransformException {
// //
//
// Get the original envelope 2d and its spatial reference system
//
// //
if (spatialReferenceSystem2D == null) {
if (coverageCRS.getCoordinateSystem().getDimension() != 2) {
spatialReferenceSystem2D = CRS.getHorizontalCRS(coverageCRS);
assert spatialReferenceSystem2D.getCoordinateSystem()
.getDimension() == 2;
baseEnvelope2D = new Envelope2D(
CRS
.transform(
CRS
.findMathTransform(
coverageCRS,
(CoordinateReferenceSystem) spatialReferenceSystem2D),
coverageEnvelope));
} else {
spatialReferenceSystem2D = coverageCRS;
baseEnvelope2D = new Envelope2D(coverageEnvelope);
}
}
}
/**
* Compute the WGS84 version for the envelope of this coverage
*
* @throws FactoryException
* @throws TransformException
*/
private void initWGS84BaseEnvelope() throws FactoryException,
TransformException {
synchronized (this) {
if (wgs84BaseEnvelope2D == null)
wgs84BaseEnvelope2D = (Envelope2D) getEnvelopeAsWGS84(
coverageEnvelope, true);
}
}
/**
* Get a WGS84 envelope for the specified envelope. The get2D parameter
* allows to specify if we need the returned coverage as an
* {@code Envelope2D} or a more general {@code GeneralEnvelope} instance.
*
*
* @param envelope
* @param get2D
* if {@code true}, the requested envelope will be an
* instance of {@link Envelope2D}. If {@code false} it will
* be an instance of {@link GeneralEnvelope
* @return a WGS84 envelope as {@link Envelope2D} in case of request for a
* 2D WGS84 Envelope, or a {@link GeneralEnvelope} otherwise.
* @throws FactoryException
* @throws TransformException
*/
private Envelope getEnvelopeAsWGS84(Envelope envelope, boolean get2D)
throws FactoryException, TransformException {
Envelope requestedWGS84;
final MathTransform transformToWGS84;
final CoordinateReferenceSystem crs = envelope
.getCoordinateReferenceSystem();
// //
//
// get a math transform to go to WGS84
//
// //
if (!CRS.equalsIgnoreMetadata(crs, DefaultGeographicCRS.WGS84)) {
transformToWGS84 = CRS.findMathTransform(crs,
DefaultGeographicCRS.WGS84, true);
} else {
transformToWGS84 = IdentityTransform.create(2);
}
// do we need to transform the requested envelope?
if (!transformToWGS84.isIdentity()) {
GeneralEnvelope env = CRS.transform(transformToWGS84, envelope);
if (get2D) {
requestedWGS84 = new Envelope2D(env);
((Envelope2D) requestedWGS84)
.setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
} else {
requestedWGS84 = env;
((GeneralEnvelope) requestedWGS84)
.setCoordinateReferenceSystem(DefaultGeographicCRS.WGS84);
}
} else {
if (get2D)
return new Envelope2D(envelope);
else
return new GeneralEnvelope(envelope);
}
return requestedWGS84;
}
/**
* This method is responsible for evaluating possible subsampling factors
* once the best resolution level has been found in case we have support for
* overviews, or starting from the original coverage in case there are no
* overviews available.
*
* @param readP
* the imageRead parameter to be set
* @param requestedRes
* the requested resolutions from which to determine the
* decimation parameters.
*/
protected void setDecimationParameters(ImageReadParam readP,
double[] requestedRes) {
{
final int w = baseGridRange.getSpan(0);
final int h = baseGridRange.getSpan(1);
// ///////////////////////////////////////////////////////////////
// DECIMATION ON READING
// Setting subsampling factors with some checkings
// 1) the subsampling factors cannot be zero
// 2) the subsampling factors cannot be such that the w or h are 0
// ///////////////////////////////////////////////////////////////
if (requestedRes == null) {
readP.setSourceSubsampling(1, 1, 0, 0);
} else {
int subSamplingFactorX = (int) Math.floor(requestedRes[0]
/ highestRes[0]);
subSamplingFactorX = (subSamplingFactorX == 0) ? 1
: subSamplingFactorX;
while (((w / subSamplingFactorX) <= 0)
&& (subSamplingFactorX >= 0))
subSamplingFactorX--;
subSamplingFactorX = (subSamplingFactorX == 0) ? 1
: subSamplingFactorX;
int subSamplingFactorY = (int) Math.floor(requestedRes[1]
/ highestRes[1]);
subSamplingFactorY = (subSamplingFactorY == 0) ? 1
: subSamplingFactorY;
while (((h / subSamplingFactorY) <= 0)
&& (subSamplingFactorY >= 0))
subSamplingFactorY--;
subSamplingFactorY = (subSamplingFactorY == 0) ? 1
: subSamplingFactorY;
readP.setSourceSubsampling(subSamplingFactorX,
subSamplingFactorY, 0, 0);
}
}
}
/**
* Retrieves the original grid to world transformation for this
* {@link AbstractGridCoverage2DReader}.
*
* @param pixInCell
* specifies the datum of the transformation we want.
* @return the original grid to world transformation
*/
public MathTransform getOriginalGridToWorld(final PixelInCell pixInCell) {
// we do not have to change the pixel datum
if (pixInCell == PixelInCell.CELL_CENTER)
return raster2Model;
// we do have to change the pixel datum
if (raster2Model instanceof AffineTransform) {
final AffineTransform tr = new AffineTransform(
(AffineTransform) raster2Model);
tr.concatenate(AffineTransform.getTranslateInstance(-0.5, -0.5));
return ProjectiveTransform.create(tr);
}
if (raster2Model instanceof IdentityTransform) {
final AffineTransform tr = new AffineTransform(1, 0, 0, 1, 0, 0);
tr.concatenate(AffineTransform.getTranslateInstance(-0.5, -0.5));
return ProjectiveTransform.create(tr);
}
throw new IllegalStateException(
"This grid to world transform is invalud!");
}
/**
* Returns the intersection between the base envelope and the requested
* envelope.
*
* @param requestedEnvelope2D
* the requested 2D envelope to be intersected with the base
* envelope.
* @param requestedDim
* is the requested region where to load data of the
* specified envelope.
* @param readGridToWorld
* the Grid to world transformation to be used in read
* @return the resulting intersection of envelopes. In case of empty
* intersection, this method is allowed to return {@code null}
* @throws TransformException
* @throws FactoryException
*/
private GeneralEnvelope getIntersection(
GeneralEnvelope requestedEnvelope2D, Rectangle requestedDim,
MathTransform2D readGridToWorld) throws TransformException,
FactoryException {
GeneralEnvelope adjustedRequestedEnvelope = new GeneralEnvelope(2);
final CoordinateReferenceSystem requestedEnvelopeCRS2D = requestedEnvelope2D
.getCoordinateReferenceSystem();
boolean tryWithWGS84 = false;
try {
// convert the requested envelope 2D to this coverage native crs.
MathTransform transform = null;
if (!CRS.equalsIgnoreMetadata(requestedEnvelopeCRS2D,
spatialReferenceSystem2D))
transform = CRS.findMathTransform(requestedEnvelopeCRS2D,
spatialReferenceSystem2D, true);
// now transform the requested envelope to source crs
if (transform != null && !transform.isIdentity())
adjustedRequestedEnvelope = CRS.transform(transform,
requestedEnvelope2D);
else
adjustedRequestedEnvelope.setEnvelope(requestedEnvelope2D);
// intersect the requested area with the bounds of this
// layer in native crs
if (!adjustedRequestedEnvelope.intersects(baseEnvelope2D, true))
return null;
adjustedRequestedEnvelope.intersect(baseEnvelope2D);
adjustedRequestedEnvelope
.setCoordinateReferenceSystem(spatialReferenceSystem2D);
// //
//
// transform the intersection envelope from the destination world
// space to the requested raster space
//
// //
final Envelope requestedEnvelopeCropped = (transform != null && !transform
.isIdentity()) ? CRS.transform(transform.inverse(),
adjustedRequestedEnvelope) : adjustedRequestedEnvelope;
final Rectangle2D ordinates = CRS.transform(
readGridToWorld.inverse(), requestedEnvelopeCropped)
.toRectangle2D();
final GeneralGridEnvelope finalRange = new GeneralGridEnvelope(ordinates
.getBounds());
final Rectangle tempRect = finalRange.toRectangle();
// check that we stay inside the source rectangle
XRectangle2D.intersect(tempRect, requestedDim, tempRect);
requestedDim.setRect(tempRect);
} catch (TransformException te) {
// something bad happened while trying to transform this
// envelope. let's try with wgs84
tryWithWGS84 = true;
} catch (FactoryException fe) {
// something bad happened while trying to transform this
// envelope. let's try with wgs84
tryWithWGS84 = true;
}
// //
//
// If this does not work, we go back to reproject in the wgs84
// requested envelope
//
// //
if (tryWithWGS84) {
initWGS84BaseEnvelope();
final GeneralEnvelope requestedEnvelopeWGS84 = (GeneralEnvelope) getEnvelopeAsWGS84(
requestedEnvelope2D, false);
// checking the intersection in wgs84
if (!requestedEnvelopeWGS84.intersects(wgs84BaseEnvelope2D, true))
return null;
// intersect
adjustedRequestedEnvelope = new GeneralEnvelope(
requestedEnvelopeWGS84);
adjustedRequestedEnvelope.intersect(wgs84BaseEnvelope2D);
adjustedRequestedEnvelope = CRS.transform(CRS.findMathTransform(
requestedEnvelopeWGS84.getCoordinateReferenceSystem(),
spatialReferenceSystem2D, true), adjustedRequestedEnvelope);
adjustedRequestedEnvelope
.setCoordinateReferenceSystem(spatialReferenceSystem2D);
}
return adjustedRequestedEnvelope;
}
/**
* @return
* @uml.property name="hints"
*/
public Hints getHints() {
return hints;
}
/**
* @return
* @uml.property name="useMultithreading"
*/
public boolean useMultithreading() {
return useMultithreading;
}
/**
* @return
* @uml.property name="imageReadParam"
*/
public ImageReadParam getImageReadParam() {
return imageReadParam;
}
/**
* @return
* @uml.property name="useJAI"
*/
public boolean useJAI() {
return useJAI;
}
/**
* @return
* @uml.property name="needTransformation"
*/
public boolean needTransformation() {
return needTransformation;
}
/**
* @return
* @uml.property name="emptyRequest"
*/
public boolean isEmptyRequest() {
return emptyRequest;
}
/**
* @return
* @uml.property name="input"
*/
public File getInput() {
return input;
}
/**
* @return
* @uml.property name="raster2Model"
*/
public MathTransform getRaster2Model() {
return raster2Model;
}
/**
* @return
* @uml.property name="coverageEnvelope"
*/
public GeneralEnvelope getCoverageEnvelope() {
return coverageEnvelope;
}
/**
* @return
* @uml.property name="coverageCRS"
*/
public CoordinateReferenceSystem getCoverageCRS() {
return coverageCRS;
}
/**
* @return
* @uml.property name="coverageName"
*/
public String getCoverageName() {
return coverageName;
}
public int getImageIndex() {
return imageIndex;
}
public void setImageIndex(int imageIndex) {
this.imageIndex = imageIndex;
}
public void setScaleAndOffset(double[] scaleAndOffset) {
this.scaleAndOffset = scaleAndOffset.clone();
}
public double[] getScaleAndOffset() {
return scaleAndOffset;
}
public double[] getValidRange() {
return validRange;
}
public void setValidRange(double[] validRange) {
this.validRange = validRange.clone();
}
public String getLongName() {
return longName;
}
public void setLongName(String longName) {
this.longName = longName;
}
public void setNoDataValues(double[] noDataValues) {
this.noDataValues = noDataValues;
}
public double[] getNoDataValues() {
return noDataValues;
}
public Unit getUnit() {
return unit;
}
public void setUnit(Unit unit) {
this.unit = unit;
}
}