/*
* 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.core.CoreCommonImageMetadata;
import it.geosolutions.imageio.imageioimpl.imagereadmt.ImageReadDescriptorMT;
import it.geosolutions.imageio.ndplugin.BaseImageMetadata;
import it.geosolutions.imageio.stream.input.FileImageInputStreamExt;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReader;
import javax.imageio.metadata.IIOMetadata;
import javax.imageio.spi.ImageReaderSpi;
import javax.measure.unit.Unit;
import javax.media.jai.JAI;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.ViewType;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.io.util.Utilities;
import org.geotools.data.DataSourceException;
import org.geotools.data.DefaultResourceInfo;
import org.geotools.data.DefaultServiceInfo;
import org.geotools.data.ResourceInfo;
import org.geotools.data.ServiceInfo;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.imageio.SpatioTemporalImageReader;
import org.geotools.resources.coverage.CoverageUtilities;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.coverage.grid.GridRange;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
/**
* Base class for GridCoverage data access
*
* @author Daniele Romagnoli, GeoSolutions
* @author Simone Giannecchini, GeoSolutions
*
*
* @source $URL: http://svn.osgeo.org/geotools/branches/2.7.x/build/maven/javadoc/../../../modules/unsupported/coverage-experiment/coverage-core/src/main/java/org/geotools/coverage/io/BaseGridCoverage2DReader.java $
*/
@SuppressWarnings("deprecation")
public abstract class BaseGridCoverage2DReader extends
AbstractGridCoverage2DReader implements GridCoverageReader {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(BaseGridCoverage2DReader.class.toString());
/** registering ImageReadMT JAI operation (for multithreaded ImageRead) */
static {
ImageReadDescriptorMT.register(JAI.getDefaultInstance());
}
final static protected boolean INFO_DEBUG = Boolean
.getBoolean("org.geotools.imageio.debug");
private int imageIndex = 0;
/** Caches an {@code ImageReaderSpi}. */
private final ImageReaderSpi readerSPI;
private double[] scaleAndOffset;
private double[] validRange;
private double[] noDataValues;
private String longName;
private Unit<?> unit;
private int gridCoverageCount;
/**
* Implement this method to setup the coverage properties (Envelope,
* CoordinateReferenceSystem, GridRange) using the provided
* {@code ImageReader}
*/
protected abstract void setCoverageProperties(ImageReader reader)
throws IOException;
// ////////////////////////////////////////////////////////////////////////
//
// Referencing related fields (CoordinateReferenceSystem and Envelope)
//
// ////////////////////////////////////////////////////////////////////////
/** Envelope read from file */
private GeneralEnvelope coverageEnvelope = null;
/** The CoordinateReferenceSystem for the coverage */
private CoordinateReferenceSystem coverageCRS = null;
// ////////////////////////////////////////////////////////////////////////
//
// Data source properties and field for management
//
// ////////////////////////////////////////////////////////////////////////
/** Source given as input to the reader */
private File inputFile = null;
/** Coverage name */
private String coverageName = "geotools_coverage";
/**
* The base {@link GridRange} for the {@link GridCoverage2D} of this reader.
*/
private GeneralGridEnvelope coverageGridRange = null;
/** Absolute path to the parent dir for this coverage. */
private String parentPath;
/**
* Creates a new instance of a {@link BaseGridCoverage2DReader}. I assume
* nothing about file extension.
*
* @param input
* Source object for which we want to build a
* {@link BaseGridCoverage2DReader}.
* @param hints
*
* Hints to be used by this reader throughout his life.
* @param formatSpecificSpi
* an instance of a proper {@code ImageReaderSpi}.
* @throws DataSourceException
*/
protected BaseGridCoverage2DReader(Object input, final Hints hints,
final ImageReaderSpi formatSpecificSpi) throws DataSourceException {
try {
// //
//
// managing hints
//
// //
if (this.hints == null)
this.hints = new Hints();
if (hints != null)
this.hints.add(hints);
this.coverageFactory = CoverageFactoryFinder.getGridCoverageFactory(this.hints);
readerSPI = formatSpecificSpi;
// //
//
// Source management
//
// //
checkSource(input);
final ImageReader reader = readerSPI.createReaderInstance();
reader.setInput(inputFile);
// //
//
// Setting Envelope, GridRange and CoordinateReferenceSystem
//
// //
setCoverageProperties(reader);
if (getCoverageCRS() == null) {
LOGGER.info("crs not found, proceeding with EPSG:4326");
setCoverageCRS(AbstractGridFormat.getDefaultCRS());
}
if (getCoverageEnvelope() == null) {
throw new DataSourceException(
"Unavailable envelope for this coverage");
}
// Additional settings due to "final" methods getOriginalXXX
originalEnvelope = getCoverageEnvelope();
originalGridRange = getCoverageGridRange();
crs = getCoverageCRS();
// //
//
// Information about multiple levels and such
//
// //
getResolutionInfo(reader);
// //
//
// Reset and dispose reader
//
// //
reader.reset();
} catch (IOException e) {
if (LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
throw new DataSourceException(e);
} catch (TransformException e) {
if (LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
throw new DataSourceException(e);
}
}
/** Package scope highest resolution info accessor */
double[] getHighestRes() {
return highestRes;
}
/** Package scope hints accessor */
Hints getHints() {
return hints;
}
/** Package scope grid to world transformation accessor */
MathTransform getRaster2Model() {
return raster2Model;
}
/**
* Checks the input provided to this {@link BaseGridCoverage2DReader} and
* sets all the other objects and flags accordingly.
*
* @param input
* provided to this {@link BaseGridCoverage2DReader}.
*
* @param hints
* Hints to be used by this reader throughout his life.
*
* @throws UnsupportedEncodingException
* @throws DataSourceException
* @throws IOException
* @throws FileNotFoundException
*/
protected void checkSource(Object input)
throws UnsupportedEncodingException, DataSourceException,
IOException, FileNotFoundException {
if (input == null) {
final DataSourceException ex = new DataSourceException(
"No source set to read this coverage.");
if (LOGGER.isLoggable(Level.SEVERE)) {
LOGGER.log(Level.SEVERE, ex.getLocalizedMessage(), ex);
}
throw ex;
}
this.source = null;
// //
//
// URL to FIle
//
// //
// if it is an URL pointing to a File I convert it to a file.
if (input instanceof URL) {
// URL that point to a file
final URL sourceURL = ((URL) input);
this.source = sourceURL;
if (sourceURL.getProtocol().compareToIgnoreCase("file") == 0) {
final File fileCheck = new File(URLDecoder.decode(sourceURL
.getFile(), "UTF-8"));
final String path = fileCheck.getAbsolutePath();
final int imageSpecifierIndex = path.lastIndexOf(":");
final File file;
if (imageSpecifierIndex > 1
&& imageSpecifierIndex > path.indexOf(":")) {
file = new File(path.substring(0, imageSpecifierIndex));
try {
imageIndex = Integer.parseInt(path.substring(
imageSpecifierIndex + 1, path.length()));
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER
.log(Level.WARNING,
"Unable to parse the specified 0-based imageIndex");
}
}
} else
file = fileCheck;
inputFile = file;
input = file;
} else {
throw new IllegalArgumentException("Unsupported input type");
}
}
if (input instanceof FileImageInputStreamExt) {
if (source == null) {
source = input;
}
inputFile = ((FileImageInputStreamExt) input).getFile();
input = inputFile;
}
// //
//
// File
//
// //
if (input instanceof File) {
final File fileCheck = (File) input;
final String path = fileCheck.getAbsolutePath();
final int imageSpecifierIndex = path.lastIndexOf(":");
final File file;
if (imageSpecifierIndex > 1
&& imageSpecifierIndex > path.indexOf(":")) {
file = new File(path.substring(0, imageSpecifierIndex));
try {
imageIndex = Integer.parseInt(path.substring(
imageSpecifierIndex + 1, path.length()));
} catch (NumberFormatException nfe) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER
.log(Level.WARNING,
"Unable to parse the specified 0-based imageIndex");
}
}
} else
file = fileCheck;
final File sourceFile = file;
if (source == null) {
source = sourceFile;
}
if (inputFile == null) {
inputFile = sourceFile;
}
if (!sourceFile.exists() || sourceFile.isDirectory()
|| !sourceFile.canRead()) {
throw new DataSourceException(
"Provided file does not exist or is a directory or is not readable!");
}
this.parentPath = sourceFile.getParent();
coverageName = sourceFile.getName();
final int dotIndex = coverageName.indexOf(".");
coverageName = (dotIndex == -1) ? coverageName : coverageName
.substring(0, dotIndex);
} else {
throw new IllegalArgumentException("Unsupported input type");
}
}
/**
* Gets resolution information about the coverage itself.
*
* @param reader
* an {@link ImageReader} to use for getting the resolution
* information.
* @throws IOException
* @throws TransformException
* @throws DataSourceException
*/
private void getResolutionInfo(ImageReader reader) throws IOException,
TransformException {
// //
//
// get the dimension of the high resolution image and compute the
// resolution
//
// //
final Rectangle originalDim = new Rectangle(0, 0, reader
.getWidth(imageIndex), reader.getHeight(imageIndex));
if (getCoverageGridRange() == null) {
setCoverageGridRange(new GeneralGridEnvelope(originalDim));
}
// ///
//
// setting the higher resolution available for this coverage
//
// ///
highestRes = CoverageUtilities
.getResolution((AffineTransform) raster2Model);
if (LOGGER.isLoggable(Level.FINE))
LOGGER
.fine(new StringBuffer("Highest Resolution = [").append(
highestRes[0]).append(",").append(highestRes[1])
.toString());
}
/**
* Returns a {@link GridCoverage} from this reader in compliance with the
* specified parameters.
*
* @param params
* a {@code GeneralParameterValue} array to customize the
* read operation.
*/
public GridCoverage2D read(GeneralParameterValue[] params)
throws IllegalArgumentException, IOException {
// Setup a new coverage request
final BaseCoverageRequest request = new BaseCoverageRequest(params, this);
// compute the request.
GridCoverage2D gc = ((GridCoverage2D) (requestCoverage(request)
.getGridCoverage()));
if (validRange != null)
return gc.view(ViewType.GEOPHYSICS);
return gc;
}
/**
* Information about this source. Subclasses should provide additional
* format specific information.
*
* @return ServiceInfo describing getSource().
*/
public ServiceInfo getInfo() {
DefaultServiceInfo info = new DefaultServiceInfo();
info.setDescription(source.toString());
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 gridcoverage.
*
* @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;
}
/**
* Returns a {@link BaseCoverageResponse} from the specified
* {@link BaseCoverageRequest}.
*
* @param request
* a previously set {@link BaseCoverageRequest} defining a set of
* parameters to get a specific coverage
* @return the computed {@code BaseCoverageResponse}
* @todo Future versions may cache requests<->responses using hashing
*/
private BaseCoverageResponse requestCoverage(BaseCoverageRequest request) {
final BaseCoverageResponse response = new BaseCoverageResponse(request,
coverageFactory, readerSPI);
try {
response.compute();
} catch (IOException e) {
LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
return null;
}
return response;
}
/**
* @param coverageCRS
* the coverageCRS to set
*/
protected void setCoverageCRS(CoordinateReferenceSystem coverageCRS) {
this.coverageCRS = coverageCRS;
}
/**
* @return the coverageCRS
*/
protected CoordinateReferenceSystem getCoverageCRS() {
return coverageCRS;
}
/**
* @param coverageEnvelope
* the coverageEnvelope to set
*/
protected void setCoverageEnvelope(GeneralEnvelope coverageEnvelope) {
this.coverageEnvelope = coverageEnvelope;
}
/**
* @return the coverageEnvelope
*/
protected GeneralEnvelope getCoverageEnvelope() {
return coverageEnvelope;
}
/**
* @param coverageGridRange
* the coverageGridRange to set
*/
protected void setCoverageGridRange(GeneralGridEnvelope coverageGridRange) {
this.coverageGridRange = coverageGridRange;
}
/**
* @return the coverageGridRange
*/
protected GeneralGridEnvelope getCoverageGridRange() {
return coverageGridRange;
}
/**
* @return the input file
*/
protected File getInputFile() {
return inputFile;
}
/**
* @return the coverage name
*/
public String getCoverageName() {
return coverageName;
}
protected void setInputFile(File inputFile) {
this.inputFile = inputFile;
}
protected void setCoverageName(String coverageName) {
this.coverageName = coverageName;
}
protected String getParentPath() {
return parentPath;
}
protected void setParentPath(String parentPath) {
this.parentPath = parentPath;
}
protected ImageReaderSpi getReaderSPI() {
return readerSPI;
}
protected int getImageIndex() {
return imageIndex;
}
protected String getLongName() {
return longName;
}
protected void setLongName(String longName) {
this.longName = longName;
}
protected double[] getNoDataValues() {
return noDataValues != null ? noDataValues.clone() : null;
}
protected void setNoDataValues(double[] noDataValues) {
this.noDataValues = noDataValues;
}
protected double[] getScaleAndOffset() {
return scaleAndOffset != null ? scaleAndOffset.clone() : null;
}
protected void setScaleAndOffset(double[] scaleAndOffset) {
this.scaleAndOffset = scaleAndOffset;
}
protected double[] getValidRange() {
return validRange != null ? validRange.clone() : null;
}
protected void setValidRange(double[] validRange) {
this.validRange = validRange;
}
protected void setGridCoverageCount(int gridCoverageCount) {
this.gridCoverageCount = gridCoverageCount;
}
/**
* Note: although this method returns the number of datasets available by
* means of the underlying imageReader, use a single Reader for a single
* dataset.
*
*/
public int getGridCoverageCount() {
return gridCoverageCount;
}
protected Unit<?> getUnit() {
return unit;
}
protected void setUnit(Unit<?> unit) {
this.unit = unit;
}
/**
* @param spatioTemporalReader
* @throws IOException
*
* @todo: TODO XXX When defining new Geotools reader, fill these properties
* using the spatioTemporal metadata instance, getting values from
* the Band node instead of querying the commonMetadata
* object
*/
protected void setInternalParameters(
SpatioTemporalImageReader spatioTemporalReader) throws IOException {
final int imageIndex = getImageIndex();
IIOMetadata metadata = spatioTemporalReader.getImageMetadata(imageIndex);
double scale = 1.0;
double offset = 0.0;
double[] validRange = null;
double[] noDataValues = null;
if (metadata != null && metadata instanceof BaseImageMetadata) {
BaseImageMetadata commonMetadata = (BaseImageMetadata) metadata;
try {
scale = commonMetadata.getScale(0);
offset = commonMetadata.getOffset(0);
} catch (IllegalArgumentException iae) {
// TODO: no scale and offset are available
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Using default scale and offset values");
}
}
setScaleAndOffset(new double[] { scale, offset });
try {
double max = commonMetadata.getMaximum(0);
double min = commonMetadata.getMinimum(0);
validRange = new double[] { min, max };
} catch (IllegalArgumentException iae) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("ValidRange values not found");
}
}
if (validRange != null)
setValidRange(validRange);
try {
double noData = commonMetadata.getNoDataValue(0);
noDataValues = new double[] { noData };
} catch (IllegalArgumentException iae) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("NoData values not found");
}
}
if (noDataValues != null)
setNoDataValues(noDataValues);
setLongName(commonMetadata.getDatasetName());
}
}
protected void setCommonMetadata(IIOMetadata metadata) {
if (metadata instanceof CoreCommonImageMetadata) {
Node root = metadata.getAsTree(CoreCommonImageMetadata.nativeMetadataFormatName);
Node commonChild = root.getFirstChild();
while (!commonChild.getNodeName().equalsIgnoreCase("RasterDimensions"))
commonChild = commonChild.getNextSibling();
NamedNodeMap attributes = commonChild.getAttributes();
// //
//
// 1) Grid
//
// //
if (getCoverageGridRange() == null) {
final String width = attributes.getNamedItem("width").getNodeValue();
final String height = attributes.getNamedItem("height").getNodeValue();
if (Utilities.ensureValidString(width,height)) {
final int w = Integer.parseInt(width);
final int h = Integer.parseInt(height);
setCoverageGridRange(new GeneralGridEnvelope(new Rectangle(0, 0, w, h)));
}
}
}
}
}