/*
* 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.gce.imagecollection;
import java.awt.image.ColorModel;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.jai.PlanarImage;
import org.apache.commons.io.FilenameUtils;
import org.geotools.coverage.Category;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.factory.Hints;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.referencing.CRS;
import org.opengis.coverage.ColorInterpretation;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;
/**
*
*
*
* @source $URL: http://svn.osgeo.org/geotools/trunk/modules/unsupported/imagecollection/src/main/java/org/geotools/gce/imagecollection/ImageCollectionReader.java $
*/
public final class ImageCollectionReader extends AbstractGridCoverage2DReader implements GridCoverageReader {
/** Logger for the {@link ImageCollectionReader} class. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ImageCollectionReader.class.toString());
class DefaultsValue {
String path;
int maxWidth = 65536;
int maxHeight = 65536;
/**
* Time which needs to elapse between 2 consecutive checks for a file changes
* (Milliseconds)
*/
long timeBetweenChecks = 1000 * 600;
int epsgCode = Utils.IMAGE_EPSG;
GeneralEnvelope envelope = null;
boolean isGeoSpatial = false;
}
DefaultsValue defaultValues = new DefaultsValue();
/**
* Number of coverages for this reader is
*
* @return the number of coverages for this reader.
*/
@Override
public int getGridCoverageCount() {
return 1;
}
private RasterManager rasterManager;
URL sourceURL;
String rootPath;
// RasterLayout[] overViewLayouts;
// RasterLayout hrLayout;
boolean expandMe = true;
@Override
public void dispose() {
super.dispose();
rasterManager.dispose();
}
/**
* Let us retrieve the {@link GridCoverageFactory} that we want to use.
*
* @return retrieves the {@link GridCoverageFactory} that we want to use.
*/
GridCoverageFactory getGridCoverageFactory() {
return coverageFactory;
}
/**
* Creates a new instance of ImageCollectionReader
*
* @param input
* the input folder
* @throws DataSourceException
*/
public ImageCollectionReader(Object input) throws DataSourceException {
this(input, null);
}
/**
* Creates a new instance of ImageCollectionReader
*
* @param input
* the input folder
* @param uHints
* user-supplied hints TODO currently are unused
* @throws DataSourceException
*/
public ImageCollectionReader(Object input, Hints uHints)
throws DataSourceException {
super(input, uHints);
//
// Set the source being careful in case it is an URL pointing to a file
//
File file = null;
try {
this.sourceURL = Utils.checkSource(source);
source = DataUtilities.urlToFile(sourceURL);
if (source instanceof File){
file = (File) source;
rootPath = FilenameUtils.getFullPath(FilenameUtils.normalizeNoEndSeparator(file.getAbsolutePath()) + Utils.SEPARATOR);
loadConfig();
}
} catch (IOException e) {
throw new DataSourceException(e);
}
rasterManager = new RasterManager(this);
getHRInfo();
}
/**
* Setup basic referencing info.
*/
private void getHRInfo() {
originalGridRange = rasterManager.getCoverageGridrange();
originalEnvelope = rasterManager.getCoverageEnvelope();
originalEnvelope.setCoordinateReferenceSystem(rasterManager.getCoverageCRS());
crs = rasterManager.getCoverageCRS();
raster2Model = rasterManager.getRaster2Model();
highestRes = rasterManager.highestRes.clone();
}
/**
* Setup collection configuration by checking for a properties file containing basic info such as
* coverageName, relative path of the default image, expand RGB flag, timeBetweenChecks.
* @throws FileNotFoundException
*/
private void loadConfig() throws FileNotFoundException {
final String propertiesPath = rootPath + Utils.CONFIG_FILE;
final File propertiesFile = new File(propertiesPath);
String coverage = null;
final boolean hasPropertiesFile = Utils.checkFileReadable(propertiesFile);
if (hasPropertiesFile){
// //
//
// STEP 0:
// Loading configuration from properties file
//
// //
Properties props = new Properties();
FileInputStream fis = null;
try {
fis = new FileInputStream(propertiesFile);
props.load(fis);
coverage = initProperties(props);
} catch (FileNotFoundException e) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to parse the config file: " + propertiesPath, e);
}
} catch (IOException e) {
if (LOGGER.isLoggable(Level.WARNING)) {
LOGGER.log(Level.WARNING, "Unable to parse the config file: " + propertiesPath, e);
}
} finally {
if (fis != null) {
try {
fis.close();
} catch (Throwable t) {
// Does nothing
}
}
}
}
// //
//
// STEP 1:
// Setting parameter which haven't been found in the property file
//
// //
if (coverage == null){
coverageName = FilenameUtils.getBaseName(FilenameUtils.getFullPathNoEndSeparator(rootPath));
} else {
coverageName = coverage;
}
if (defaultValues.path == null){
final File parent = new File(rootPath);
final List<File> files;
if (parent.exists() && parent.isDirectory() && parent.canRead()){
files = Utils.getFileList(parent, Utils.FILE_FILTER, true);
if (!files.isEmpty()){
String path = files.get(0).getAbsolutePath();
defaultValues.path = path;
if (path.startsWith(rootPath)){
defaultValues.path = path.substring(rootPath.length());
}
}
}
}
if (!hasPropertiesFile){
updatePropertiesFile(propertiesFile);
}
}
/**
* Init coverage properties from the provided Properties file
* @param props
* @return
*/
private String initProperties(Properties props) {
String coverage = null;
// Getting coverage name
if (props.containsKey(Utils.ImageCollectionProperties.COVERAGE_NAME)) {
final String coverageName = (String) props.get(Utils.ImageCollectionProperties.COVERAGE_NAME);
if (coverageName != null && coverageName.trim().length() > 0) {
coverage = coverageName;
}
}
// Getting default path
if (props.containsKey(Utils.ImageCollectionProperties.DEFAULT_PATH)) {
final String defaultPath = (String) props.get(Utils.ImageCollectionProperties.DEFAULT_PATH);
if (defaultPath != null && defaultPath.trim().length() > 0) {
defaultValues.path = defaultPath;
}
}
// Getting expand to rgb property (used to deal with paletted images)
if (props.containsKey(Utils.ImageCollectionProperties.EXPAND_RGB)) {
final String expand = (String) props.get(Utils.ImageCollectionProperties.EXPAND_RGB);
if (expand != null && expand.trim().length() > 0) {
this.expandMe = Boolean.parseBoolean(expand);
}
}
// Getting timeIntervalCheck.
if (props.containsKey(Utils.ImageCollectionProperties.TIME_BETWEEN_CHECKS)) {
final String timeCheck = (String) props.get(Utils.ImageCollectionProperties.TIME_BETWEEN_CHECKS);
if (timeCheck != null && timeCheck.trim().length() > 0) {
try {
defaultValues.timeBetweenChecks = Long.parseLong(timeCheck) * 1000;
} catch (NumberFormatException nfe){
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified time interval check.", nfe);
}
}
}
}
// Getting MaxWidth parameter.
if (props.containsKey(Utils.ImageCollectionProperties.MAX_WIDTH)) {
final String maxW = (String) props.get(Utils.ImageCollectionProperties.MAX_WIDTH);
if (maxW != null && maxW.trim().length() > 0) {
try {
defaultValues.maxWidth = Integer.parseInt(maxW);
} catch (NumberFormatException nfe){
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified Max Width property.", nfe);
}
}
}
}
// Getting MaxHeight parameter.
if (props.containsKey(Utils.ImageCollectionProperties.MAX_HEIGHT)) {
final String maxH = (String) props.get(Utils.ImageCollectionProperties.MAX_HEIGHT);
if (maxH != null && maxH.trim().length() > 0) {
try {
defaultValues.maxHeight = Integer.parseInt(maxH);
} catch (NumberFormatException nfe){
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified Max Height property.", nfe);
}
}
}
}
//TODO: Modify this once we get support for CRS with y as DISPLAY_DOWN
if (props.containsKey(Utils.ImageCollectionProperties.EPSG_CODE)) {
final String epsgCode = (String) props.get(Utils.ImageCollectionProperties.EPSG_CODE);
if (epsgCode != null && epsgCode.trim().length() > 0) {
try {
defaultValues.epsgCode = Integer.parseInt(epsgCode);
if (defaultValues.epsgCode != Utils.IMAGE_EPSG){
defaultValues.isGeoSpatial = true;
CoordinateReferenceSystem crs = CRS.decode("EPSG:" + epsgCode);
boolean envelopeIsSet = false;
String env = null;
if (props.containsKey(Utils.ImageCollectionProperties.ENVELOPE)) {
env = (String) props.get(Utils.ImageCollectionProperties.ENVELOPE);
final String coords[] = env.trim().split(" ");
if (coords.length == 2){
final String coordsMin[] = coords[0].trim().split(",");
final String coordsMax[] = coords[1].trim().split(",");
if (coordsMin.length == 2 && coordsMax.length == 2){
GeneralEnvelope envelope = new GeneralEnvelope(
new double[]{Double.parseDouble(coordsMin[0].trim()),Double.parseDouble(coordsMin[1].trim())},
new double[]{Double.parseDouble(coordsMax[0].trim()),Double.parseDouble(coordsMax[1].trim())});
envelope.setCoordinateReferenceSystem(crs);
defaultValues.envelope = envelope;
envelopeIsSet = true;
}
}
}
if ((!envelopeIsSet) && LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified envelope. minx,miny,maxx,maxy coordinates are needed: " + env);
}
}
} catch (NumberFormatException nfe){
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified EPSG code: " + epsgCode, nfe);
}
} catch (NoSuchAuthorityCodeException e) {
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified EPSG code: " + epsgCode, e);
}
} catch (FactoryException e) {
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to parse the specified EPSG code: " + epsgCode, e);
}
}
}
}
return coverage;
}
private void updatePropertiesFile(File propertiesFile) {
if (!propertiesFile.exists()){
FileOutputStream fos = null;
try {
fos = new FileOutputStream(propertiesFile);
Properties prop = new Properties();
prop.put(Utils.ImageCollectionProperties.DEFAULT_PATH, defaultValues.path);
prop.store(fos, null);
} catch (IOException e) {
if (LOGGER.isLoggable(Level.WARNING)){
LOGGER.log(Level.WARNING, "Unable to store properties in a config.property file ", e );
}
} finally {
if (fos != null){
try {
fos.close();
fos = null;
} catch (Throwable t){
//Doesn nothing
}
}
}
}
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#getFormat()
*/
public Format getFormat() {
return new ImageCollectionFormat();
}
/**
* @see org.opengis.coverage.grid.GridCoverageReader#read(org.opengis.parameter.GeneralParameterValue[])
*/
@Override
public GridCoverage2D read(GeneralParameterValue[] params)
throws IOException {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Reading image from " + sourceURL.toString() + "\nHighest res "
+ highestRes[0] + " " + highestRes[1]);
}
final Collection<GridCoverage2D> response = rasterManager.read(params);
if (response.isEmpty()){
if (LOGGER.isLoggable(Level.FINE)){
LOGGER.fine("The response is empty. ==> returning a null GridCoverage");
}
return null;
} else
return response.iterator().next();
}
/**
* 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 GridCoverage createCoverage(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");
Category[] categories = null;
bands[i] = new GridSampleDimension(colorInterpretation.name(), categories, null).geophysics(true);
}
// creating coverage
if (raster2Model != null) {
return coverageFactory.create(coverageName, image, crs, raster2Model, bands, null, null);
}
return coverageFactory.create(coverageName, image, new GeneralEnvelope(
originalEnvelope), bands, null, null);
}
/**
* Package private accessor for {@link Hints}.
*
* @return this {@link Hints} used by this reader.
*/
Hints getHints() {
return super.hints;
}
/**
* Package private accessor for the highest resolution values.
*
* @return the highest resolution values.
*/
double[] getHighestRes() {
return super.highestRes;
}
/**
*
* @return
*/
double[][] getOverviewsResolution() {
return super.overViewResolutions;
}
int getNumberOfOverviews() {
return super.numOverviews;
}
String getName() {
return super.coverageName;
}
}