/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2005-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.grid.io;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.ImageTypeSpecifier;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.stream.ImageInputStream;
import javax.media.jai.ImageLayout;
import javax.media.jai.PlanarImage;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultServiceInfo;
import org.geotools.data.ServiceInfo;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
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.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Utilities;
import org.geotools.util.logging.Logging;
import org.jaitools.imageutils.ImageLayout2;
import org.opengis.coverage.ColorInterpretation;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.InvalidParameterNameException;
import org.opengis.parameter.InvalidParameterValueException;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterNotFoundException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.CoordinateOperation;
import org.opengis.referencing.operation.CoordinateOperationFactory;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
/**
* This class is a first attempt for providing a way to get more informations out of a single 2D raster datasets (x,y). It is worth to remark that for
* the moment this is thought for 2D rasters not for 3D or 4D rasters (x,y,z,t).
*
* <p>
* The main drawback I see with the current GeoApi GridCoverageReader interface is that there is no way to get real information about a raster source
* unless you instantiate a GridCoverage. As an instance it is impossible to know the envelope, the number of overviews, the tile size. This
* information is needed in order to perform decimation on reading or to use built-in overviews<br>
* This really impacts the ability to exploit raster datasets in a desktop environment where caching is crucial.
*
* @author Simone Giannecchini, GeoSolutions
* @since 2.3
*
*
* @source $URL$
*/
public abstract class AbstractGridCoverage2DReader implements GridCoverage2DReader {
/** The {@link Logger} for this {@link AbstractGridCoverage2DReader}. */
private final static Logger LOGGER = Logging.getLogger("org.geotools.data.coverage.grid");
/**
* This contains the number of overviews.aaa
*/
protected int numOverviews = 0;
/** 2DGridToWorld math transform. */
protected MathTransform raster2Model = null;
/** crs for this coverage */
protected CoordinateReferenceSystem crs = null;
/** Envelope read from file */
protected GeneralEnvelope originalEnvelope = null;
/** Coverage name */
protected String coverageName = "geotools_coverage";
/** Source to read from */
protected Object source = null;
/** Hints used by the {@link AbstractGridCoverage2DReader} subclasses. */
protected Hints hints = GeoTools.getDefaultHints();
/**
* Highest resolution availaible for this reader.
*/
protected double[] highestRes = null;
/** Temp variable used in many readers. */
protected boolean closeMe;
/**
* In case we are trying to read from a GZipped file this will be set to true.
*/
protected boolean gzipped;
/**
* The original {@link GridRange} for the {@link GridCoverage2D} of this reader.
*/
protected GridEnvelope originalGridRange = null;
/**
* Input stream that can be used to initialize subclasses of {@link AbstractGridCoverage2DReader}.
*/
protected ImageInputStream inStream = null;
/** Resolutions avialaible through an overviews based mechanism. */
protected double[][] overViewResolutions = null;
/**
* {@link GridCoverageFactory} instance.
*/
protected GridCoverageFactory coverageFactory;
private Map<String,ArrayList<Resolution>> resolutionsLevelsMap = new HashMap<String,ArrayList<Resolution>>();
protected ImageInputStreamSpi inStreamSPI;
private ImageLayout imageLayout;
/**
* Default protected constructor. Useful for wrappers.
*/
protected AbstractGridCoverage2DReader() {
}
/**
* Creates a new instance of a {@link AIGReader}. I assume nothing about file extension.
*
* @param input Source object for which we want to build an {@link AIGReader}.
* @throws DataSourceException
*/
public AbstractGridCoverage2DReader(Object input) throws DataSourceException {
this(input, null);
}
/**
* Creates a new instance of a {@link AIGReader}. I assume nothing about file extension.
*
* @param input Source object for which we want to build an {@link AIGReader}.
* @param hints Hints to be used by this reader throughout his life.
* @throws DataSourceException
*/
public AbstractGridCoverage2DReader(Object input, Hints hints) throws DataSourceException {
//
// basic management of hints
//
if (hints == null)
this.hints = new Hints();
if (hints != null) {
this.hints = hints.clone();
}
// GridCoverageFactory initialization
if (this.hints.containsKey(Hints.GRID_COVERAGE_FACTORY)) {
final Object factory = this.hints.get(Hints.GRID_COVERAGE_FACTORY);
if (factory != null && factory instanceof GridCoverageFactory) {
this.coverageFactory = (GridCoverageFactory) factory;
}
}
if (this.coverageFactory == null) {
this.coverageFactory = CoverageFactoryFinder.getGridCoverageFactory(this.hints);
}
//
// Setting input
//
if (input == null) {
final IOException ex = new IOException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,
"input"));
throw new DataSourceException(ex);
}
this.source = input;
}
/**
* This method is responsible for checking the provided coverage name against the coverage name for this {@link GridCoverage2DReader}.
*
* @param coverageName the coverage name to check.
* @return <code>true</code> if this {@link GridCoverage2DReader} contains the provided coverage name, <code>false</code> otherwise.
*/
protected boolean checkName(String coverageName) {
Utilities.ensureNonNull("coverageName", coverageName);
return coverageName.equalsIgnoreCase(this.coverageName);
}
@Override
public GridCoverage2D read(String coverageName, GeneralParameterValue[] parameters)
throws IllegalArgumentException, IOException {
// Default implementation for backwards compatibility
if (coverageName.equalsIgnoreCase(this.coverageName)) {
return read(parameters);
}
// Subclasses should do more checks on coverageName
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
/**
* Read the current grid coverage from the stream.
* <p>
* Example:
*
* <pre>
* <code>
* </code>
* </pre>
*
* The method {@link #hasMoreGridCoverages} should be invoked first in order to verify that a coverage is available.
*
* @param parameters Optional parameters matching {@link Format#getReadParameters}.
* @return a {@linkplain GridCoverage grid coverage} from the input source.
* @throws InvalidParameterNameException if a parameter in {@code parameters} doesn't have a recognized name.
* @throws InvalidParameterValueException if a parameter in {@code parameters} doesn't have a valid value.
* @throws ParameterNotFoundException if a parameter was required for the operation but was not provided in the {@code parameters} list.
* @throws CannotCreateGridCoverageException if the coverage can't be created for a logical reason (for example an unsupported format, or an
* inconsistency found in the data).
* @throws IOException if a read operation failed for some other input/output reason, including {@link FileNotFoundException} if no file with the
* given {@code name} can be found, or {@link javax.imageio.IIOException} if an error was thrown by the underlying image library.
*/
public abstract GridCoverage2D read(GeneralParameterValue[] parameters)
throws IllegalArgumentException, IOException;
// -------------------------------------------------------------------------
//
// old support methods
//
// -------------------------------------------------------------------------
/**
* This method is responsible for preparing the read param for doing an {@link ImageReader#read(int, ImageReadParam)}.
*
*
* <p>
* This method is responsible for preparing the read param 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. It also returns and {@link Integer} representing the index of the raster to be read when dealing with multipage raster.
*
* @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 readP an instance of {@link ImageReadParam} for setting the subsampling factors.
* @param requestedEnvelope the {@link GeneralEnvelope} we are requesting.
* @param requestedDim the requested dimensions.
* @return the index of the raster to read in the underlying data source.
* @throws IOException
* @throws TransformException
*/
protected Integer setReadParams(OverviewPolicy overviewPolicy, ImageReadParam readP,
GeneralEnvelope requestedEnvelope, Rectangle requestedDim) throws IOException, TransformException {
return setReadParams(coverageName, overviewPolicy, readP, requestedEnvelope, requestedDim);
}
/**
* This method is responsible for preparing the read param for doing an {@link ImageReader#read(int, ImageReadParam)}.
*
*
* <p>
* This method is responsible for preparing the read param 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. It also returns and {@link Integer} representing the index of the raster to be read when dealing with multipage raster.
*
* @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 readP an instance of {@link ImageReadParam} for setting the subsampling factors.
* @param requestedEnvelope the {@link GeneralEnvelope} we are requesting.
* @param requestedDim the requested dimensions.
* @return the index of the raster to read in the underlying data source.
* @throws IOException
* @throws TransformException
*/
protected Integer setReadParams(String coverageName, OverviewPolicy overviewPolicy,
ImageReadParam readP, GeneralEnvelope requestedEnvelope, Rectangle requestedDim)
throws IOException, TransformException {
// //
//
// Default image index 0
//
// //
Integer imageChoice = new Integer(0);
// //
//
// Init overview policy
//
// //
// when policy is explictly provided it overrides the policy provided
// using hints.
if (overviewPolicy == null)
overviewPolicy = extractOverviewPolicy();
// //
//
// default values for subsampling
//
// //
readP.setSourceSubsampling(1, 1, 0, 0);
// //
//
// requested to ignore overviews
//
// //
if (overviewPolicy.equals(OverviewPolicy.IGNORE))
return imageChoice;
// //
//
// Am I going to decimate or to use overviews? If this file has only
// one page we use decimation, otherwise we use the best page available.
// Future versions should use both.
//
// //
final boolean useOverviews = (numOverviews > 0) ? true : false;
// //
//
// Resolution requested. I am here computing the resolution required by
// the user.
//
// //
double[] requestedRes = getResolution(requestedEnvelope, requestedDim,
getCoordinateReferenceSystem(coverageName));
if (requestedRes == null)
return imageChoice;
// //
//
// overviews or decimation
//
// //
if (useOverviews)
imageChoice = pickOverviewLevel(coverageName, overviewPolicy, requestedRes);
// /////////////////////////////////////////////////////////////////////
// DECIMATION ON READING
// /////////////////////////////////////////////////////////////////////
decimationOnReadingControl(coverageName, imageChoice, readP, requestedRes);
return imageChoice;
}
/**
* This method is responsible for checking the overview policy as defined by the provided {@link Hints}.
*
* @return the overview policy which can be one of {@link Hints#VALUE_OVERVIEW_POLICY_IGNORE}, {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST},
* {@link Hints#VALUE_OVERVIEW_POLICY_SPEED}, {@link Hints#VALUE_OVERVIEW_POLICY_QUALITY}. Default is
* {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST}.
*/
private OverviewPolicy extractOverviewPolicy() {
OverviewPolicy overviewPolicy = null;
// check if a policy was provided using hints (check even the
// deprecated one)
if (this.hints != null)
if (this.hints.containsKey(Hints.OVERVIEW_POLICY))
overviewPolicy = (OverviewPolicy) this.hints.get(Hints.OVERVIEW_POLICY);
// use default if not provided. Default is nearest
if (overviewPolicy == null)
overviewPolicy = OverviewPolicy.getDefaultPolicy();
assert overviewPolicy != null;
return overviewPolicy;
}
private Integer pickOverviewLevel(String coverageName, OverviewPolicy policy, double[] requestedRes) {
// setup policy
if (policy == null)
policy = extractOverviewPolicy();
ArrayList<Resolution> resolutionsLevels;
// sort resolutions from smallest pixels (higher res) to biggest pixels (higher res)
// keeping a reference to the original image choice
synchronized (this) {
resolutionsLevels = resolutionsLevelsMap.get(coverageName);
if (resolutionsLevels == null) {
resolutionsLevels = new ArrayList<Resolution>();
resolutionsLevelsMap.put(coverageName, resolutionsLevels);
// note that we assume what follows:
// -highest resolution image is at level 0.
// -all the overviews share the same envelope
// -the aspect ratio for the overviews is constant
// -the provided resolutions are taken directly from the grid
resolutionsLevels.add(new Resolution(1, getHighestRes()[0], getHighestRes()[1], 0));
if (numOverviews > 0) {
for (int i = 0; i < overViewResolutions.length; i++)
resolutionsLevels.add(new Resolution(overViewResolutions[i][0]
/ getHighestRes()[0], overViewResolutions[i][0],
overViewResolutions[i][1], i + 1));
Collections.sort(resolutionsLevels);
}
}
}
// Now search for the best matching resolution.
// Check also for the "perfect match"... unlikely in practice unless someone
// tunes the clients to request exactly the resolution embedded in
// the overviews, something a perf sensitive person might do in fact
// the requested resolutions
final double reqx = requestedRes[0];
final double reqy = requestedRes[1];
// requested scale factor for least reduced axis
final Resolution max = resolutionsLevels.get(0);
final double requestedScaleFactorX = reqx / max.resolutionX;
final double requestedScaleFactorY = reqy / max.resolutionY;
final int leastReduceAxis = requestedScaleFactorX <= requestedScaleFactorY ? 0 : 1;
final double requestedScaleFactor = leastReduceAxis == 0 ? requestedScaleFactorX
: requestedScaleFactorY;
// are we looking for a resolution even higher than the native one?
if (requestedScaleFactor <= 1)
return max.imageChoice;
// are we looking for a resolution even lower than the smallest overview?
final Resolution min = resolutionsLevels.get(resolutionsLevels.size() - 1);
if (requestedScaleFactor >= min.scaleFactor)
return min.imageChoice;
// Ok, so we know the overview is between min and max, skip the first
// and search for an overview with a resolution lower than the one requested,
// that one and the one from the previous step will bound the searched resolution
Resolution prev = max;
final int size = resolutionsLevels.size();
for (int i = 1; i < size; i++) {
final Resolution curr = resolutionsLevels.get(i);
// perfect match check
if (curr.scaleFactor == requestedScaleFactor) {
return curr.imageChoice;
}
// middle check. The first part of the condition should be sufficient, but
// there are cases where the x resolution is satisfied by the lowest resolution,
// the y by the one before the lowest (so the aspect ratio of the request is
// different than the one of the overviews), and we would end up going out of the loop
// since not even the lowest can "top" the request for one axis
if (curr.scaleFactor > requestedScaleFactor || i == size - 1) {
if (policy == OverviewPolicy.QUALITY)
return prev.imageChoice;
else if (policy == OverviewPolicy.SPEED)
return curr.imageChoice;
else if (requestedScaleFactor - prev.scaleFactor < curr.scaleFactor
- requestedScaleFactor)
return prev.imageChoice;
else
return curr.imageChoice;
}
prev = curr;
}
// fallback
return max.imageChoice;
}
/**
* Returns the actual resolution used to read the data given the specified target resolution and the specified overview policy
*
* @param policy
* @param resolutions
* @return
* @throws IOException
*/
public double[] getReadingResolutions(OverviewPolicy policy, double[] requestedResolution) throws IOException {
// Default implementation for backwards compatibility
return getReadingResolutions(coverageName, policy, requestedResolution);
}
/**
* Returns the actual resolution used to read the data given the specified target resolution and the specified overview policy
*
* @param policy
* @param resolutions
* @return
*/
@Override
public double[] getReadingResolutions(String coverageName, OverviewPolicy policy,
double[] requestedResolution) throws IOException {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
// find the target resolution level
double[] result;
if (numOverviews > 0) {
int imageIdx = pickOverviewLevel(coverageName, policy, requestedResolution);
result = imageIdx > 0 ? overViewResolutions[imageIdx - 1] : highestRes;
} else {
result = getHighestRes();
}
// return via cloning to protect internal state
double[] clone = new double[result.length];
System.arraycopy(result, 0, clone, 0, result.length);
return clone;
}
/**
* Simple support class for sorting overview resolutions
*
* @author Andrea Aime
* @author Simone Giannecchini, GeoSolutions.
* @since 2.5
*/
private static class Resolution implements Comparable<Resolution> {
double scaleFactor;
double resolutionX;
double resolutionY;
int imageChoice;
public Resolution(final double scaleFactor, final double resolutionX,
final double resolutionY, int imageChoice) {
this.scaleFactor = scaleFactor;
this.resolutionX = resolutionX;
this.resolutionY = resolutionY;
this.imageChoice = imageChoice;
}
public int compareTo(Resolution other) {
if (scaleFactor > other.scaleFactor)
return 1;
else if (scaleFactor < other.scaleFactor)
return -1;
else
return 0;
}
public String toString() {
return "Resolution[Choice=" + imageChoice + ",scaleFactor=" + scaleFactor + "]";
}
}
protected final void decimationOnReadingControl(Integer imageChoice, ImageReadParam readP, double[] requestedRes) {
decimationOnReadingControl(imageChoice, readP, requestedRes);
}
/**
* 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.
*
* Anyhow this method should not be called directly but subclasses should make use of the setReadParams method instead in order to transparently
* look for overviews.
*
* @param imageChoice
* @param readP
* @param requestedRes
*/
protected final void decimationOnReadingControl(String coverageName, Integer imageChoice, ImageReadParam readP, double[] requestedRes) {
{
int w, h;
double selectedRes[] = new double[2];
final int choice = imageChoice.intValue();
if (choice == 0) {
// highest resolution
w = getOriginalGridRange(coverageName).getSpan(0);
h = getOriginalGridRange(coverageName).getSpan(1);
selectedRes[0] = getHighestRes()[0];
selectedRes[1] = getHighestRes()[1];
} else {
// some overview
selectedRes[0] = overViewResolutions[choice - 1][0];
selectedRes[1] = overViewResolutions[choice - 1][1];
w = (int) Math.round(getOriginalEnvelope(coverageName).getSpan(0) / selectedRes[0]);
h = (int) Math.round(getOriginalEnvelope(coverageName).getSpan(1) / selectedRes[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
// zero
// /////////////////////////////////////////////////////////////////////
if (requestedRes == null) {
readP.setSourceSubsampling(1, 1, 0, 0);
} else {
int subSamplingFactorX = (int) Math.floor(requestedRes[0] / selectedRes[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] / selectedRes[1]);
subSamplingFactorY = subSamplingFactorY == 0 ? 1 : subSamplingFactorY;
while (h / subSamplingFactorY <= 0 && subSamplingFactorY >= 0)
subSamplingFactorY--;
subSamplingFactorY = subSamplingFactorY == 0 ? 1 : subSamplingFactorY;
readP.setSourceSubsampling(subSamplingFactorX, subSamplingFactorY, 0, 0);
}
}
}
/**
* Creates a {@link GridCoverage} for the provided {@link PlanarImage} using the {@link #originalEnvelope} that was provided for this coverage.
*
* @param image contains the data for the coverage to create.
* @return a {@link GridCoverage}
* @throws IOException
*/
protected final GridCoverage createImageCoverage(PlanarImage image) throws IOException {
return createImageCoverage(coverageName, image);
}
/**
* Creates a {@link GridCoverage} for the provided {@link PlanarImage} using the {@link #originalEnvelope} that was provided for this coverage.
*
* @param coverageName
* @param image contains the data for the coverage to create.
* @return a {@link GridCoverage}
* @throws IOException
*/
protected final GridCoverage createImageCoverage(String coverageName, PlanarImage image)
throws IOException {
return createImageCoverage(coverageName, image, null);
}
/**
* Creates a {@link GridCoverage} for the provided {@link PlanarImage} using the {@link #raster2Model} that was provided for this coverage.
*
* <p>
* This method is vital when working with coverages that have a raster to model transformation that is not a simple scale and translate.
*
* @param image contains the data for the coverage to create.
* @param raster2Model is the {@link MathTransform} that maps from the raster space to the model space.
* @return a {@link GridCoverage}
* @throws IOException
*/
protected final GridCoverage2D createImageCoverage(PlanarImage image, MathTransform raster2Model)
throws IOException {
return createImageCoverage(coverageName, image, raster2Model);
}
/**
* Creates a {@link GridCoverage} for the provided {@link PlanarImage} using the {@link #raster2Model} that was provided for this coverage.
*
* <p>
* This method is vital when working with coverages that have a raster to model transformation that is not a simple scale and translate.
*
* @param coverageName
* @param image contains the data for the coverage to create.
* @param raster2Model is the {@link MathTransform} that maps from the raster space to the model space.
* @return a {@link GridCoverage}
* @throws IOException
*/
protected final GridCoverage2D createImageCoverage(String coverageName, PlanarImage image,
MathTransform raster2Model) throws IOException {
// creating bands
final SampleModel sm = image.getSampleModel();
final ColorModel cm = image.getColorModel();
final int numBands = sm.getNumBands();
final GridSampleDimension[] bands = new GridSampleDimension[numBands];
// setting bands names.
for (int i = 0; i < numBands; i++) {
final ColorInterpretation colorInterpretation = TypeMap.getColorInterpretation(cm, i);
if (colorInterpretation == null)
throw new IOException("Unrecognized sample dimension type");
bands[i] = new GridSampleDimension(colorInterpretation.name()).geophysics(true);
}
// creating coverage
if (raster2Model != null) {
return coverageFactory.create(coverageName, image,
getCoordinateReferenceSystem(coverageName), raster2Model, bands, null, null);
}
return coverageFactory.create(coverageName, image, new GeneralEnvelope(getOriginalEnvelope(coverageName)),
bands, null, null);
}
/**
* This method is responsible for computing the resolutions in for the provided grid geometry in the provided crs.
*
* <P>
* It is worth to note that the returned resolution array is of length of 2 and it always is lon, lat for the moment.<br>
* It might be worth to remove the axes reordering code when we are confident enough with the code to handle the north-up crs.
* <p>
* TODO use orthodromic distance?
*
* @param envelope the GeneralEnvelope
* @param dim
* @param crs
* @throws DataSourceException
*/
protected final static double[] getResolution(GeneralEnvelope envelope, Rectangle2D dim,
CoordinateReferenceSystem crs) throws DataSourceException {
double[] requestedRes = null;
try {
if (dim != null && envelope != null && crs != null) {
// do we need to transform the originalEnvelope?
final CoordinateReferenceSystem envelopeCrs2D = CRS.getHorizontalCRS(envelope
.getCoordinateReferenceSystem());
if (envelopeCrs2D != null && !CRS.equalsIgnoreMetadata(crs, envelopeCrs2D)) {
CoordinateOperationFactory operationFactory = CRS
.getCoordinateOperationFactory(true);
CoordinateOperation op = operationFactory.createOperation(envelopeCrs2D, crs);
envelope = CRS.transform(op, envelope);
envelope.setCoordinateReferenceSystem(crs);
}
requestedRes = new double[2];
requestedRes[0] = envelope.getSpan(0) / dim.getWidth();
requestedRes[1] = envelope.getSpan(1) / dim.getHeight();
}
return requestedRes;
} catch (TransformException e) {
throw new DataSourceException("Unable to get resolution", e);
} catch (FactoryException e) {
throw new DataSourceException("Unable to get resolution", e);
}
}
/**
* Retrieves the {@link CoordinateReferenceSystem} for dataset pointed by this {@link AbstractGridCoverage2DReader}.
*
* @return the {@link CoordinateReferenceSystem} for dataset pointed by this {@link AbstractGridCoverage2DReader}.
* @deprecated use {@link #getCoordinateReferenceSystem()}
*/
public final CoordinateReferenceSystem getCrs() {
return getCoordinateReferenceSystem();
}
/**
* Retrieves the {@link GeneralGridEnvelope} that represents the raster grid dimensions of the highest resolution level in this dataset.
*
* @return the {@link GeneralGridEnvelope} that represents the raster grid dimensions of the highest resolution level in this dataset.
*/
public GridEnvelope getOriginalGridRange() {
return getOriginalGridRange(coverageName);
}
/**
* Retrieves the {@link GeneralGridEnvelope} that represents the raster grid dimensions of the highest resolution level in this dataset.
*
* @return the {@link GeneralGridEnvelope} that represents the raster grid dimensions of the highest resolution level in this dataset.
*/
@Override
public GridEnvelope getOriginalGridRange(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
assert originalGridRange.getDimension() == 2;
return new GridEnvelope2D(originalGridRange.getLow(0), originalGridRange.getLow(1),
originalGridRange.getSpan(0), originalGridRange.getSpan(1));
}
/**
* Retrieves the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*
* @return the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*/
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem() {
// Default implementation for backwards compatibility
return getCoordinateReferenceSystem(coverageName);
}
/**
* Retrieves the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*
* @return the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*/
@Override
public CoordinateReferenceSystem getCoordinateReferenceSystem(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return crs;
}
/**
* Retrieves the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*
* @return the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*/
public GeneralEnvelope getOriginalEnvelope() {
return getOriginalEnvelope(coverageName);
}
/**
* Retrieves the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*
* @return the {@link GeneralEnvelope} for this {@link AbstractGridCoverage2DReader}.
*/
@Override
public GeneralEnvelope getOriginalEnvelope(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return new GeneralEnvelope(originalEnvelope);
}
/**
* 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 for this {@link AbstractGridCoverage2DReader}.
*/
public MathTransform getOriginalGridToWorld(final PixelInCell pixInCell) {
// Default implementation for backwards compatibility
return getOriginalGridToWorld(coverageName, pixInCell);
}
/**
* 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 for this {@link AbstractGridCoverage2DReader}.
*/
@Override
public MathTransform getOriginalGridToWorld(String coverageName, PixelInCell pixInCell) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
synchronized (this) {
if (raster2Model == null) {
final GridToEnvelopeMapper geMapper = new GridToEnvelopeMapper(
getOriginalGridRange(coverageName), getOriginalEnvelope(coverageName));
geMapper.setPixelAnchor(PixelInCell.CELL_CENTER);
raster2Model = geMapper.createTransform();
}
}
// 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 reader's grid to world transform is invalud!");
}
/**
* Retrieves the source for this {@link AbstractGridCoverage2DReader}.
*
* @return the source for this {@link AbstractGridCoverage2DReader}.
*/
public final Object getSource() {
return source;
}
/**
* Disposes this reader.
*
* <p>
* This method just tries to close the underlying {@link ImageInputStream}.
*/
public void dispose() {
if (inStream != null && closeMe) {
try {
inStream.close();
} catch (IOException e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
}
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#skip()
* @deprecated no replacement for that method
*/
public void skip() {
throw new UnsupportedOperationException("Unsupported operation.");
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#hasMoreGridCoverages()
* @deprecated no replacement for that method
*/
public boolean hasMoreGridCoverages() {
throw new UnsupportedOperationException("Unsupported operation.");
}
/**
* @deprecated use {@link #getGridCoverageNames()}
*/
public String[] listSubNames() {
return getGridCoverageNames();
}
@Override
public String[] getGridCoverageNames() {
return new String[] { coverageName };
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#getCurrentSubname()
* @deprecated no replacement for that method
*/
public String getCurrentSubname() {
throw new UnsupportedOperationException("Unsupported operation.");
}
public String[] getMetadataNames(final String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return getMetadataNames();
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#getMetadataNames()
*/
public String[] getMetadataNames() {
return null;
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#getMetadataValue(java.lang.String)
*/
public String getMetadataValue(final String name) {
return getMetadataValue(coverageName, name);
}
public String getMetadataValue(final String coverageName, final String name) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return null;
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#getGridCoverageCount()
*/
public int getGridCoverageCount() {
return 1;
}
/**
* Information about this source.
* <p>
* Subclasses should provide additional format specific information.
*
* @return ServiceInfo describing getSource().
*/
public ServiceInfo getInfo() {
DefaultServiceInfo info = new DefaultServiceInfo();
info.setDescription(source == null ? null : String.valueOf(source));
if (source instanceof URL) {
URL url = (URL) source;
info.setTitle(url.getFile());
try {
info.setSource(url.toURI());
} catch (URISyntaxException e) {
}
} else if (source instanceof File) {
File file = (File) source;
String filename = file.getName();
if (filename == null || filename.length() == 0) {
info.setTitle(file.getName());
}
info.setSource(file.toURI());
}
return info;
}
// /**
// * Information about the named gridcoveage.
// *
// * @param subname Name indicing grid coverage to describe
// * @return ResourceInfo describing grid coverage indicated
// */
// public ResourceInfo getInfo( String subname ){
// DefaultResourceInfo info = new DefaultResourceInfo();
// info.setName( subname );
// info.setBounds( new ReferencedEnvelope( this.getOriginalEnvelope()));
// info.setCRS( this.getCrs() );
// info.setTitle( subname );
// return info;
// }
/**
* Forcing disposal of this {@link AbstractGridCoverage2DReader} which may keep an {@link ImageInputStream} open.
*/
@Override
protected void finalize() throws Throwable {
dispose();
super.finalize();
}
/**
* Return the set of dynamic parameterDescriptors (the ones related to domains) for this reader. Default implementation returns an empty set of
* parameters
*
* @return
*/
@SuppressWarnings("rawtypes")
public Set<ParameterDescriptor<List>> getDynamicParameters() throws IOException {
return getDynamicParameters(coverageName);
}
/**
* Return the set of dynamic parameterDescriptors (the ones related to domains) for this reader. Default implementation returns an empty set of
* parameters
*
* @return
*/
@SuppressWarnings("rawtypes")
public Set<ParameterDescriptor<List>> getDynamicParameters(String coverageName) throws IOException {
return Collections.emptySet();
}
@Override
public int getNumOverviews(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return overViewResolutions != null ? overViewResolutions.length : 0;
}
@Override
public int getNumOverviews() {
// Default implementation for backwards compatibility
return getNumOverviews(coverageName);
}
public GridEnvelope getOverviewGridEnvelope(int overviewIndex) throws IOException {
return getOverviewGridEnvelope(coverageName, overviewIndex);
}
public GridEnvelope getOverviewGridEnvelope(String coverageName, int overviewIndex)
throws IOException {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
// Default implementation for backwards compatibility
return null;
}
@Override
public ImageLayout getImageLayout(String coverageName) throws IOException {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return (ImageLayout) imageLayout.clone();
}
@Override
public ImageLayout getImageLayout() throws IOException {
// Default implementation for backwards compatibility
return getImageLayout(coverageName);
}
/**
* Extract the ImageLayout from the provided reader for the first available image.
*
* @param reader an istance of {@link ImageReader}
* @throws IOException in case an error occurs
*/
protected void setLayout(ImageReader reader) throws IOException {
Utilities.ensureNonNull("reader", reader);
// save ImageLayout
ImageLayout2 layout = new ImageLayout2();
ImageTypeSpecifier its = reader.getImageTypes(0).next();
layout.setColorModel(its.getColorModel()).setSampleModel(its.getSampleModel());
layout.setMinX(0).setMinY(0).setWidth(reader.getWidth(0)).setHeight(reader.getHeight(0));
layout.setTileGridXOffset(0).setTileGridYOffset(0).setTileWidth(reader.getTileWidth(0))
.setTileHeight(reader.getTileHeight(0));
setlayout(layout);
}
/**
* Set the provided layout for this {@link GridCoverage2DReader}-
*
* @param layout the {@link ImageLayout} to set. It must be nont null
*/
protected void setlayout(ImageLayout layout) {
Utilities.ensureNonNull("layout", layout);
this.imageLayout = (ImageLayout) layout.clone();
}
@Override
public double[][] getResolutionLevels() throws IOException {
// Default implementation for backwards compatibility
return getResolutionLevels(coverageName);
}
@Override
public double[][] getResolutionLevels(String coverageName) throws IOException {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
final double[][] returnValue = new double[numOverviews + 1][2];
double[] hres = getHighestRes();
if (hres == null) {
return null;
} else {
System.arraycopy(hres, 0, returnValue[0], 0, 2);
for (int i = 1; i < returnValue.length; i++) {
System.arraycopy(overViewResolutions[i - 1], 0, returnValue[i], 0, 2);
}
return returnValue;
}
}
protected double[] getHighestRes(String coverageName) {
if (!checkName(coverageName)) {
throw new IllegalArgumentException("The specified coverageName " + coverageName
+ "is not supported");
}
return highestRes;
}
double[] getHighestRes() {
return getHighestRes(coverageName);
}
}