/*
* GeoTools - The Open Source Java GIS Toolkit
* http://geotools.org
*
* (C) 2006-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.imagemosaic;
import java.awt.Color;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.coverage.grid.io.imageio.GeoToolsWriteParams;
import org.geotools.data.DataAccessFactory.Param;
import org.geotools.data.DataStore;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataUtilities;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.data.simple.SimpleFeatureSource;
import org.geotools.factory.Hints;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalog;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.parameter.DefaultParameterDescriptorGroup;
import org.geotools.parameter.ParameterGroup;
import org.geotools.util.Converters;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverageWriter;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.parameter.GeneralParameterDescriptor;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
/**
* {@link AbstractGridFormat} subclass for controlling {@link ImageMosaicReader} creation. As the
* name says, it handles mosaic of georeferenced images, which means
* <ol>
* <li>tiff+tfw+prj</li>
* <li>jpeg+tfw+prj</li>
* <li>png+tfw+prj</li>
* <li>geotiff</li>
* </ol>
* This does not mean that you throw there a couple of images and it will do the trick no matter how
* these images are. Requirements are:
* <ul>
* <li>(almost) equal spatial resolution</li>
* <li>same number of bands</li>
* <li>same data type</li>
* <li>same projection</li>
* </ul>
* The first requirement can be relaxed a little but if they have the same spatial resolution the
* performances are much better. There are parameters that you can use to control the behaviour of
* the mosaic in terms of thresholding and transparency. They are as follows:
* <ul>
* <li>--DefaultParameterDescriptor FINAL_ALPHA = new DefaultParameterDescriptor( "FinalAlpha",
* Boolean.class, null, Boolean.FALSE)-- It asks the plugin to add transparency on the final created
* mosaic. IT simply performs a threshonding looking for areas where there is no data, i.e.,
* intensity is really low and transform them into transparent areas. It is obvious that depending
* on the nature of the input images it might interfere with the original values.</li>
* <li>---ALPHA_THRESHOLD = new DefaultParameterDescriptor( "AlphaThreshold", Double.class, null,
* new Double(1));--- Controls the transparency addition by specifying the treshold to use.</li>
* <li>INPUT_IMAGE_THRESHOLD = new DefaultParameterDescriptor( "InputImageROI", Boolean.class,
* null, Boolean.FALSE)--- INPUT_IMAGE_THRESHOLD_VALUE = new DefaultParameterDescriptor(
* "InputImageROIThreshold", Integer.class, null, new Integer(1));--- These two can be used to
* control the application of ROIs on the input images based on tresholding values. Basically using
* the threshold you can ask the mosaic plugin to load or not certain pixels of the original images.</li>
*
* @author Simone Giannecchini (simboss), GeoSolutions
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
* @since 2.3
*
* @source $URL$
*/
public final class ImageMosaicFormat extends AbstractGridFormat implements Format {
final static double DEFAULT_ARTIFACTS_FILTER_PTILE_THRESHOLD = 0.1;
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ImageMosaicFormat.class.toString());
static final Interpolation DEFAULT_INTERPOLATION = new InterpolationNearest();
/** Optional Time value for this mosaic. */
public static final ParameterDescriptor<List> TIME = DefaultParameterDescriptor.create("TIME", "A list of time objects",List.class, null,false);
/** Filter tiles based on attributes from the input coverage*/
public static final ParameterDescriptor<Filter> FILTER = new DefaultParameterDescriptor<Filter>("Filter", Filter.class, null, null);
/** Control the type of the final mosaic. */
public static final ParameterDescriptor<Boolean> FADING = new DefaultParameterDescriptor<Boolean>("Fading", Boolean.class, new Boolean[]{Boolean.TRUE,Boolean.FALSE}, Boolean.FALSE);
/** Control the transparency of the output coverage. */
public static final ParameterDescriptor<Color> OUTPUT_TRANSPARENT_COLOR = new DefaultParameterDescriptor<Color>(
"OutputTransparentColor", Color.class, null, null);
/** Control the thresholding on the input coverage */
public static final ParameterDescriptor<Integer> MAX_ALLOWED_TILES = new DefaultParameterDescriptor<Integer>(
"MaxAllowedTiles", Integer.class, null, Integer.MAX_VALUE);
/** Control the default artifact filter luminance thresholding on the input coverages */
public static final ParameterDescriptor<Integer> DEFAULT_ARTIFACTS_FILTER_THRESHOLD = new DefaultParameterDescriptor<Integer>(
"DefaultArtifactsFilterThreshold", Integer.class, null, Integer.MIN_VALUE);
/** Control the artifact filter ptile thresholding */
public static final ParameterDescriptor<Double> ARTIFACTS_FILTER_PTILE_THRESHOLD = new DefaultParameterDescriptor<Double>(
"ArtifactsFilterPtileThreshold", Double.class, null, Double.valueOf(DEFAULT_ARTIFACTS_FILTER_PTILE_THRESHOLD));
/** Control the threading behavior for this plugin.*/
public static final ParameterDescriptor<Boolean> ALLOW_MULTITHREADING = new DefaultParameterDescriptor<Boolean>(
"AllowMultithreading", Boolean.class, new Boolean[]{Boolean.TRUE,Boolean.FALSE}, Boolean.FALSE);
/** Control the footprint management.*/
public static final ParameterDescriptor<Boolean> HANDLE_FOOTPRINT = new DefaultParameterDescriptor<Boolean>(
"HandleFootprint", Boolean.class, new Boolean[]{Boolean.TRUE,Boolean.FALSE}, Boolean.TRUE);
/** Control whether to add the ROI in the output mosaic. */
public static final ParameterDescriptor<Boolean> SET_ROI_PROPERTY = new DefaultParameterDescriptor<Boolean>(
"SetRoiProperty", Boolean.class, new Boolean[]{Boolean.TRUE,Boolean.FALSE}, Boolean.FALSE);
/** Control the background values for the output coverage */
public static final ParameterDescriptor<double[]> BACKGROUND_VALUES = new DefaultParameterDescriptor<double[]>(
"BackgroundValues", double[].class, null, null);
/** Control the interpolation to be used in mosaicking */
public static final ParameterDescriptor<Interpolation> INTERPOLATION = new DefaultParameterDescriptor<Interpolation>(
"Interpolation", Interpolation.class, null, DEFAULT_INTERPOLATION);
/** Optional Elevation value for this mosaic. */
public static final ParameterDescriptor<List> ELEVATION = DefaultParameterDescriptor.create("ELEVATION", "An elevation value",List.class, null,false);
/**
* Creates an instance and sets the metadata.
*/
public ImageMosaicFormat() {
setInfo();
}
/**
* Sets the metadata information.
*/
private void setInfo() {
final HashMap<String,String> info = new HashMap<String,String> ();
info.put("name", "ImageMosaic");
info.put("description", "Image mosaicking plugin");
info.put("vendor", "Geotools");
info.put("docURL", "");
info.put("version", "1.0");
mInfo = info;
// reading parameters
readParameters = new ParameterGroup(new DefaultParameterDescriptorGroup(mInfo,
new GeneralParameterDescriptor[]{
READ_GRIDGEOMETRY2D,
INPUT_TRANSPARENT_COLOR,
OUTPUT_TRANSPARENT_COLOR,
USE_JAI_IMAGEREAD,
BACKGROUND_VALUES,
SUGGESTED_TILE_SIZE,
ALLOW_MULTITHREADING,
MAX_ALLOWED_TILES,
TIME,
ELEVATION,
FILTER
}));
// reading parameters
writeParameters = null;
}
/**
* @see org.geotools.data.coverage.grid.AbstractGridFormat#getImageioReader(Object)
*/
@Override
public ImageMosaicReader getReader( Object source ) {
return getReader(source, null);
}
/**
*
*/
@Override
public GridCoverageWriter getWriter( Object destination ) {
throw new UnsupportedOperationException("This plugin does not support writing.");
}
@Override
public boolean accepts( Object source, Hints hints ) {
Utilities.ensureNonNull("source", source);
if (source instanceof ImageMosaicDescriptor){
return checkDescriptor((ImageMosaicDescriptor)source);
} else {
return checkForUrl(source, hints);
}
}
/**
* @see org.geotools.data.coverage.grid.AbstractGridFormat#accepts(Object input)
*/
@Override
public boolean accepts( Object source ) {
return accepts(source, null);
}
/**
* Checks that the provided {@link ImageMosaicDescriptor} is well formed.
*
* @param source
* @return
*/
private boolean checkDescriptor(final ImageMosaicDescriptor source) {
//TODO: improve checks
final GranuleCatalog catalog = source.getCatalog();
final MosaicConfigurationBean configuration = source.getConfiguration();
if (configuration == null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Mosaic configuration is missing");
}
return false;
}
if (configuration.getLevels() == null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("resolution leves is unavailable ");
}
return false;
}
if (catalog == null) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.fine("Granule Catalog is unavailable ");
}
return false;
}
return true;
}
private boolean checkForUrl( Object source, Hints hints){
try {
URL sourceURL = Utils.checkSource(source, hints);
if(sourceURL == null){
return false;
}
if( source instanceof File){
File file = (File) source;
if( !file.exists() ){
return false; // file does not exist
}
}
// /////////////////////////////////////////////////////////////////////
//
// Load tiles informations, especially the bounds, which will be
// reused
//
// /////////////////////////////////////////////////////////////////////
DataStore tileIndexStore = null;
CoordinateReferenceSystem crs=null;
boolean shapefile=true;
try{
final File sourceF= DataUtilities.urlToFile(sourceURL);
if(FilenameUtils.getName(sourceF.getAbsolutePath()).equalsIgnoreCase("datastore.properties"))
{
shapefile=false;
// load spi anche check it
// read the properties file
final Properties properties = new Properties();
final FileInputStream stream = new FileInputStream(sourceF);
try {
properties.load(stream);
}
finally{
IOUtils.closeQuietly(stream);
}
// SPI
final String SPIClass=properties.getProperty("SPI");
// create a datastore as instructed
final DataStoreFactorySpi spi= (DataStoreFactorySpi) Class.forName(SPIClass).newInstance();
// get the params
final Map<String, Serializable> params = new HashMap<String, Serializable>();
final Param[] paramsInfo = spi.getParametersInfo();
for(Param p:paramsInfo){
// search for this param and set the value if found
if(properties.containsKey(p.key))
params.put(p.key, (Serializable)Converters.convert(properties.getProperty(p.key), p.type));
else
if(p.required&& p.sample==null)
{
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Required parameter missing: "+p.toString());
return false;
}
}
tileIndexStore=spi.createDataStore(params);
if(tileIndexStore==null)
return false;
}
else {
URL testPropertiesUrl=DataUtilities.changeUrlExt(sourceURL, "properties");
File testFile= DataUtilities.urlToFile(testPropertiesUrl);
if( !testFile.exists() ){
return false;
}
ShapefileDataStore store = new ShapefileDataStore(sourceURL);
store.setDbftimeZone(TimeZone.getTimeZone("UTC"));
tileIndexStore = store;
}
final String[] typeNames = tileIndexStore.getTypeNames();
if (typeNames.length <= 0)
return false;
final String typeName = typeNames[0];
if(typeName==null)
return false;
final SimpleFeatureSource featureSource = tileIndexStore.getFeatureSource(typeName);
if(featureSource==null)
return false;
final SimpleFeatureType schema = featureSource.getSchema();
if(schema==null)
return false;
crs = featureSource.getSchema().getGeometryDescriptor().getCoordinateReferenceSystem();
if(crs==null)
return false;
// /////////////////////////////////////////////////////////////////////
//
// Now look for the properties file and try to parse relevant fields
//
// /////////////////////////////////////////////////////////////////////
URL propsUrl = null;
if(shapefile)
propsUrl=DataUtilities.changeUrlExt(sourceURL, "properties");
else
{
//
// do we have a datastore properties file? It will preempt on the shapefile
//
final File parent=DataUtilities.urlToFile(sourceURL).getParentFile();
// this can be used to look for properties files that do NOT define a datastore
final File[] properties = parent.listFiles(
(FilenameFilter)
FileFilterUtils.andFileFilter(
FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("indexer.properties")),
FileFilterUtils.andFileFilter(
FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("datastore.properties")),
FileFilterUtils.makeFileOnly(FileFilterUtils.suffixFileFilter(".properties")
)
)
));
// do we have a valid datastore + mosaic properties pair?
for(File propFile:properties)
if(Utils.checkFileReadable(propFile)&&
Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile), "")!=null)
{
propsUrl=DataUtilities.fileToURL(propFile);
break;
}
}
//get the properties file
final MosaicConfigurationBean props = Utils.loadMosaicProperties(propsUrl, "location");
if(props==null)
return false;
// looking for the location attribute
final String locationAttributeName=props.getLocationAttribute();
if (schema.getDescriptor(locationAttributeName) == null)
return false;
return true;
}finally{
try {
if(tileIndexStore!=null)
tileIndexStore.dispose();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
}
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
return false;
}
}
/**
* @see AbstractGridFormat#getReader(Object, Hints)
*/
@Override
public ImageMosaicReader getReader( Object source, Hints hints ) {
try {
final ImageMosaicReader reader = new ImageMosaicReader(source, hints);
// reader.multiThreadedLoader = mtLoader;
return reader;
} catch (MalformedURLException e) {
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
return null;
} catch (IOException e) {
if (LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
return null;
}
}
/**
* Throw an exception since this plugin is readonly.
*
* @return nothing.
*/
@Override
public GeoToolsWriteParams getDefaultImageIOWriteParameters() {
throw new UnsupportedOperationException("Unsupported method.");
}
@Override
public GridCoverageWriter getWriter(Object destination, Hints hints) {
throw new UnsupportedOperationException("This plugin does not support writing.");
}
}