org.geotools.gce.imagemosaic.Utils.java Source code

Java tutorial

Introduction

Here is the source code for org.geotools.gce.imagemosaic.Utils.java

Source

/*
 *    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.gce.imagemosaic;

import java.awt.Color;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.geom.AffineTransform;
import java.awt.geom.PathIterator;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Queue;
import java.util.Set;
import java.util.TimeZone;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageReader;
import javax.imageio.stream.ImageInputStream;
import javax.media.jai.Histogram;
import javax.media.jai.RasterFactory;
import javax.media.jai.remote.SerializableRenderedImage;

import net.sf.ehcache.Cache;
import net.sf.ehcache.Element;

import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.geotools.data.DataAccessFactory.Param;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataStoreFactorySpi;
import org.geotools.data.DataUtilities;
import org.geotools.data.shapefile.ShapefileDataStoreFactory;
import org.geotools.factory.Hints;
import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder;
import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.ExceptionEvent;
import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilder.ProcessingEvent;
import org.geotools.gce.imagemosaic.catalogbuilder.CatalogBuilderConfiguration;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.util.Converters;
import org.geotools.util.Utilities;
import org.opengis.filter.sort.SortOrder;

import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;

/**
 * Sparse utilities for the various mosaic classes. I use them to extract
 * complex code from other places.
 * 
 * @author Simone Giannecchini, GeoSolutions S.A.S.
 * 
 *
 *
 * @source $URL$
 */
public class Utils {

    public final static String INDEXER_PROPERTIES = "indexer.properties";

    /** EHCache instance to cache histograms */
    private static Cache ehcache;

    /** RGB to GRAY coefficients (for Luminance computation) */
    public final static double RGB_TO_GRAY_MATRIX[][] = { { 0.114, 0.587, 0.299, 0 } };

    /** 
     * Flag indicating whether to compute optimized crop ops (instead of standard
     * mosaicking op) when possible (As an instance when mosaicking a single granule) 
     */
    final static boolean OPTIMIZE_CROP;

    static {
        final String prop = System.getProperty("org.geotools.imagemosaic.optimizecrop");
        if (prop != null && prop.equalsIgnoreCase("FALSE")) {
            OPTIMIZE_CROP = false;
        } else {
            OPTIMIZE_CROP = true;
        }
    }

    public static class Prop {
        public final static String LOCATION_ATTRIBUTE = "LocationAttribute";
        public final static String ENVELOPE2D = "Envelope2D";
        public final static String LEVELS_NUM = "LevelsNum";
        public final static String LEVELS = "Levels";
        public final static String SUGGESTED_SPI = "SuggestedSPI";
        public final static String EXP_RGB = "ExpandToRGB";
        public final static String ABSOLUTE_PATH = "AbsolutePath";
        public final static String NAME = "Name";
        public final static String FOOTPRINT_MANAGEMENT = "FootprintManagement";
        public final static String HETEROGENEOUS = "Heterogeneous";
        public static final String TIME_ATTRIBUTE = "TimeAttribute";
        public static final String ELEVATION_ATTRIBUTE = "ElevationAttribute";
        public static final String ADDITIONAL_DOMAIN_ATTRIBUTES = "AdditionalDomainAttributes";
        public final static String TYPENAME = "TypeName";
        public final static String PATH_TYPE = "PathType";
        public final static String PARENT_LOCATION = "ParentLocation";

        //Indexer Properties specific properties
        public static final String RECURSIVE = "Recursive";
        public static final String WILDCARD = "Wildcard";
        public static final String SCHEMA = "Schema";
        public static final String RESOLUTION_LEVELS = "ResolutionLevels";
        public static final String PROPERTY_COLLECTORS = "PropertyCollectors";
        public final static String CACHING = "Caching";
    }

    /**
    * Logger.
    */
    private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(Utils.class.toString());
    /**
     * Default wildcard for creating mosaics.
     */
    public static final String DEFAULT_WILCARD = "*.*";

    /**
     * Default path behavior with respect to absolute paths.
     */
    public static final boolean DEFAULT_PATH_BEHAVIOR = false;

    /**
     * Default behavior with respect to index caching.
     */
    private static final boolean DEFAULT_CACHING_BEHAVIOR = false;

    /**
     * Creates a mosaic for the provided input parameters.
     * 
     * @param location
     *            path to the directory where to gather the elements for the
     *            mosaic.
     * @param indexName
     *            name to give to this mosaic
     * @param wildcard
     *            wildcard to use for walking through files. We are using
     *            commonsIO for this task
     * @param absolutePath
     *            tells the catalogue builder to use absolute paths.
     * @param hints hints to control reader instantiations
     * @return <code>true</code> if everything is right, <code>false</code>if
     *         something bad happens, in which case the reason should be logged
     *         to the logger.
     */
    static boolean createMosaic(final String location, final String indexName, final String wildcard,
            final boolean absolutePath, final Hints hints) {

        // create a mosaic index builder and set the relevant elements
        final CatalogBuilderConfiguration configuration = new CatalogBuilderConfiguration();
        configuration.setAbsolute(absolutePath);
        configuration.setHints(hints);
        configuration.setRootMosaicDirectory(location);
        configuration.setIndexingDirectories(Arrays.asList(location));
        configuration.setIndexName(indexName);

        // create the builder
        final CatalogBuilder catalogBuilder = new CatalogBuilder(configuration);
        // this is going to help us with catching exceptions and logging them
        final Queue<Throwable> exceptions = new LinkedList<Throwable>();
        try {

            final CatalogBuilder.ProcessingEventListener listener = new CatalogBuilder.ProcessingEventListener() {

                @Override
                public void exceptionOccurred(ExceptionEvent event) {
                    final Throwable t = event.getException();
                    exceptions.add(t);
                    if (LOGGER.isLoggable(Level.SEVERE))
                        LOGGER.log(Level.SEVERE, t.getLocalizedMessage(), t);

                }

                @Override
                public void getNotification(ProcessingEvent event) {
                    if (LOGGER.isLoggable(Level.FINE))
                        LOGGER.fine(event.getMessage());

                }

            };
            catalogBuilder.addProcessingEventListener(listener);
            catalogBuilder.run();
        } catch (Throwable e) {
            LOGGER.log(Level.SEVERE, "Unable to build mosaic", e);
            return false;
        } finally {
            catalogBuilder.dispose();
        }

        // check that nothing bad happened
        if (exceptions.size() > 0)
            return false;
        return true;
    }

    public static String getMessageFromException(Exception exception) {
        if (exception.getLocalizedMessage() != null)
            return exception.getLocalizedMessage();
        else
            return exception.getMessage();
    }

    static URL checkSource(Object source) throws MalformedURLException, DataSourceException {
        return checkSource(source, null);
    }

    static MosaicConfigurationBean loadMosaicProperties(final URL sourceURL,
            final String defaultLocationAttribute) {
        return loadMosaicProperties(sourceURL, defaultLocationAttribute, null);
    }

    static MosaicConfigurationBean loadMosaicProperties(final URL sourceURL, final String defaultLocationAttribute,
            final Set<String> ignorePropertiesSet) {

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.log(Level.FINE, "Trying to load properties file from URL:" + sourceURL);
        }

        // ret value
        final MosaicConfigurationBean retValue = new MosaicConfigurationBean();
        final boolean ignoreSome = ignorePropertiesSet != null && !ignorePropertiesSet.isEmpty();

        //
        // load the properties file
        //
        URL propsURL = sourceURL;
        if (!sourceURL.toExternalForm().endsWith(".properties"))
            propsURL = DataUtilities.changeUrlExt(sourceURL, "properties");
        final Properties properties = loadPropertiesFromURL(propsURL);
        if (properties == null) {
            if (LOGGER.isLoggable(Level.INFO))
                LOGGER.info("Unable to load mosaic properties file");
            return null;
        }

        String[] pairs = null;
        String pair[] = null;

        //
        // imposed bbox is optional
        //              
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ENVELOPE2D)) {
            String bboxString = properties.getProperty(Prop.ENVELOPE2D, null);
            if (bboxString != null) {
                bboxString = bboxString.trim();
                try {
                    ReferencedEnvelope bbox = parseEnvelope(bboxString);
                    if (bbox != null)
                        retValue.setEnvelope(bbox);
                    else if (LOGGER.isLoggable(Level.INFO))
                        LOGGER.info("Cannot parse imposed bbox.");
                } catch (Exception e) {
                    if (LOGGER.isLoggable(Level.INFO))
                        LOGGER.log(Level.INFO, "Cannot parse imposed bbox.", e);
                }
            }

        }

        //
        // resolutions levels
        //              
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.LEVELS)) {
            int levelsNumber = Integer.parseInt(properties.getProperty(Prop.LEVELS_NUM, "1").trim());
            retValue.setLevelsNum(levelsNumber);
            if (!properties.containsKey(Prop.LEVELS)) {
                if (LOGGER.isLoggable(Level.INFO))
                    LOGGER.info("Required key Levels not found.");
                return null;
            }
            final String levels = properties.getProperty(Prop.LEVELS).trim();
            pairs = levels.split(" ");
            if (pairs == null || pairs.length != levelsNumber) {
                if (LOGGER.isLoggable(Level.INFO))
                    LOGGER.info("Levels number is different from the provided number of levels resoltion.");
                return null;
            }
            final double[][] resolutions = new double[levelsNumber][2];
            for (int i = 0; i < levelsNumber; i++) {
                pair = pairs[i].split(",");
                if (pair == null || pair.length != 2) {
                    if (LOGGER.isLoggable(Level.INFO))
                        LOGGER.info(
                                "OverviewLevel number is different from the provided number of levels resoltion.");
                    return null;
                }
                resolutions[i][0] = Double.parseDouble(pair[0]);
                resolutions[i][1] = Double.parseDouble(pair[1]);
            }
            retValue.setLevels(resolutions);
        }

        //
        // typename, is mandatory when we don't use shapeiles
        //              
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.TYPENAME)) {
            String typeName = properties.getProperty(Prop.TYPENAME, null);
            retValue.setTypeName(typeName);
        }

        //
        // suggested spi is optional
        //
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.SUGGESTED_SPI)) {
            if (properties.containsKey(Prop.SUGGESTED_SPI)) {
                final String suggestedSPI = properties.getProperty(Prop.SUGGESTED_SPI).trim();
                retValue.setSuggestedSPI(suggestedSPI);
            }
        }

        //
        // time attribute is optional
        //
        if (properties.containsKey(Prop.TIME_ATTRIBUTE)) {
            final String timeAttribute = properties.getProperty("TimeAttribute").trim();
            retValue.setTimeAttribute(timeAttribute);
        }

        //
        // elevation attribute is optional
        //
        if (properties.containsKey(Prop.ELEVATION_ATTRIBUTE)) {
            final String elevationAttribute = properties.getProperty(Prop.ELEVATION_ATTRIBUTE).trim();
            retValue.setElevationAttribute(elevationAttribute);
        }

        //
        // additional domain attribute is optional
        //
        if (properties.containsKey(Prop.ADDITIONAL_DOMAIN_ATTRIBUTES)) {
            final String additionalDomainAttributes = properties.getProperty(Prop.ADDITIONAL_DOMAIN_ATTRIBUTES)
                    .trim();
            retValue.setAdditionalDomainAttributes(additionalDomainAttributes);
        }

        //
        // caching
        //
        if (properties.containsKey(Prop.CACHING)) {
            String caching = properties.getProperty(Prop.CACHING).trim();
            try {
                retValue.setCaching(Boolean.valueOf(caching));
            } catch (Throwable e) {
                retValue.setCaching(Boolean.valueOf(Utils.DEFAULT_CACHING_BEHAVIOR));
            }
        }

        //
        // name is not optional
        //
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.NAME)) {
            if (!properties.containsKey(Prop.NAME)) {
                if (LOGGER.isLoggable(Level.SEVERE))
                    LOGGER.severe("Required key Name not found.");
                return null;
            }
            String coverageName = properties.getProperty(Prop.NAME).trim();
            retValue.setName(coverageName);
        }

        // need a color expansion?
        // this is a newly added property we have to be ready to the case where
        // we do not find it.
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.EXP_RGB)) {
            final boolean expandMe = Boolean.valueOf(properties.getProperty(Prop.EXP_RGB, "false").trim());
            retValue.setExpandToRGB(expandMe);
        }

        // 
        // Is heterogeneous granules mosaic
        //
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.HETEROGENEOUS)) {
            final boolean heterogeneous = Boolean
                    .valueOf(properties.getProperty(Prop.HETEROGENEOUS, "false").trim());
            retValue.setHeterogeneous(heterogeneous);
        }

        //
        // Absolute or relative path
        //
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.ABSOLUTE_PATH)) {
            final boolean absolutePath = Boolean.valueOf(properties
                    .getProperty(Prop.ABSOLUTE_PATH, Boolean.toString(Utils.DEFAULT_PATH_BEHAVIOR)).trim());
            retValue.setAbsolutePath(absolutePath);
        }

        //
        // Footprint management
        //
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.FOOTPRINT_MANAGEMENT)) {
            final boolean footprintManagement = Boolean
                    .valueOf(properties.getProperty(Prop.FOOTPRINT_MANAGEMENT, "false").trim());
            retValue.setFootprintManagement(footprintManagement);
        }

        //
        // location
        //  
        if (!ignoreSome || !ignorePropertiesSet.contains(Prop.LOCATION_ATTRIBUTE)) {
            retValue.setLocationAttribute(
                    properties.getProperty(Prop.LOCATION_ATTRIBUTE, Utils.DEFAULT_LOCATION_ATTRIBUTE).trim());
        }

        // return value
        return retValue;
    }

    /**
     * Parses a bbox in the form of MIX,MINY MAXX,MAXY
     * @param bboxString the string to parse the bbox from
     * @return a {@link ReferencedEnvelope} with the parse bbox or null
     */
    public static ReferencedEnvelope parseEnvelope(final String bboxString) {
        if (bboxString == null || bboxString.length() == 0)
            return null;

        final String[] pairs = bboxString.split(" ");
        if (pairs != null && pairs.length == 2) {

            String[] pair1 = pairs[0].split(",");
            String[] pair2 = pairs[1].split(",");
            if (pair1 != null && pair1.length == 2 && pair2 != null && pair2.length == 2)
                return new ReferencedEnvelope(Double.parseDouble(pair1[0]), Double.parseDouble(pair2[0]),
                        Double.parseDouble(pair1[1]), Double.parseDouble(pair2[1]), null);

        }
        // something bad happened
        return null;
    }

    public static Properties loadPropertiesFromURL(URL propsURL) {
        final Properties properties = new Properties();
        InputStream stream = null;
        InputStream openStream = null;
        try {
            openStream = propsURL.openStream();
            stream = new BufferedInputStream(openStream);
            properties.load(stream);
        } catch (FileNotFoundException e) {
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
            return null;
        } catch (IOException e) {
            if (LOGGER.isLoggable(Level.SEVERE))
                LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
            return null;
        } finally {

            if (stream != null)
                IOUtils.closeQuietly(stream);

            if (openStream != null)
                IOUtils.closeQuietly(openStream);

        }
        return properties;
    }

    public static IOFileFilter excludeFilters(final IOFileFilter inputFilter, IOFileFilter... filters) {
        IOFileFilter retFilter = inputFilter;
        for (IOFileFilter filter : filters) {
            retFilter = FileFilterUtils.and(retFilter, FileFilterUtils.notFileFilter(filter));
        }
        return retFilter;
    }

    /**
     * Look for an {@link ImageReader} instance that is able to read the
     * provided {@link ImageInputStream}, which must be non null.
     * 
     * <p>
     * In case no reader is found, <code>null</code> is returned.
     * 
     * @param inStream
     *            an instance of {@link ImageInputStream} for which we need to
     *            find a suitable {@link ImageReader}.
     * @return a suitable instance of {@link ImageReader} or <code>null</code>
     *         if one cannot be found.
     */
    static ImageReader getReader(final ImageInputStream inStream) {
        Utilities.ensureNonNull("inStream", inStream);
        // get a reader
        inStream.mark();
        final Iterator<ImageReader> readersIt = ImageIO.getImageReaders(inStream);
        if (!readersIt.hasNext()) {
            return null;
        }
        return readersIt.next();
    }

    /**
     * Retrieves the dimensions of the {@link RenderedImage} at index
     * <code>imageIndex</code> for the provided {@link ImageReader} and
     * {@link ImageInputStream}.
     * 
     * <p>
     * Notice that none of the input parameters can be <code>null</code> or a
     * {@link NullPointerException} will be thrown. Morevoer the
     * <code>imageIndex</code> cannot be negative or an
     * {@link IllegalArgumentException} will be thrown.
     * 
     * @param imageIndex
     *            the index of the image to get the dimensions for.
     * @param inStream
     *            the {@link ImageInputStream} to use as an input
     * @param reader
     *            the {@link ImageReader} to decode the image dimensions.
     * @return a {@link Rectangle} that contains the dimensions for the image at
     *         index <code>imageIndex</code>
     * @throws IOException
     *             in case the {@link ImageReader} or the
     *             {@link ImageInputStream} fail.
     */
    static Rectangle getDimension(final int imageIndex, final ImageReader reader) throws IOException {
        Utilities.ensureNonNull("reader", reader);
        if (imageIndex < 0)
            throw new IllegalArgumentException(Errors.format(ErrorKeys.INDEX_OUT_OF_BOUNDS_$1, imageIndex));
        return new Rectangle(0, 0, reader.getWidth(imageIndex), reader.getHeight(imageIndex));
    }

    /**
     * Default priority for the underlying {@link Thread}.
     */
    public static final int DEFAULT_PRIORITY = Thread.NORM_PRIORITY;
    /**
     * Default location attribute name.
     */
    public static final String DEFAULT_LOCATION_ATTRIBUTE = "location";

    public static final String DEFAULT_INDEX_NAME = "index";

    /**
     * Checks that a {@link File} is a real file, exists and is readable.
     * 
     * @param file
     *            the {@link File} instance to check. Must not be null.
     * 
     * @return <code>true</code> in case the file is a real file, exists and is
     *         readable; <code>false </code> otherwise.
     */
    public static boolean checkFileReadable(final File file) {
        if (LOGGER.isLoggable(Level.FINE)) {
            final String message = getFileInfo(file);
            LOGGER.fine(message);
        }
        if (!file.exists() || !file.canRead() || !file.isFile())
            return false;
        return true;
    }

    /**
     * Creates a human readable message that describe the provided {@link File} object in terms of its properties.
     * 
     * <p>
     * Useful for creating meaningful log messages.
     * 
     * @param file the {@link File} object to create a descriptive message for
     * @return a {@link String} containing a descriptive message about the provided {@link File}.
     * 
     */
    public static String getFileInfo(final File file) {
        final StringBuilder builder = new StringBuilder();
        builder.append("Checking file:").append(FilenameUtils.getFullPath(file.getAbsolutePath())).append("\n");
        builder.append("isHidden:").append(file.isHidden()).append("\n");
        builder.append("exists:").append(file.exists()).append("\n");
        builder.append("isFile").append(file.isFile()).append("\n");
        builder.append("canRead:").append(file.canRead()).append("\n");
        builder.append("canWrite").append(file.canWrite()).append("\n");
        builder.append("canExecute:").append(file.canExecute()).append("\n");
        builder.append("isAbsolute:").append(file.isAbsolute()).append("\n");
        builder.append("lastModified:").append(file.lastModified()).append("\n");
        builder.append("length:").append(file.length());
        final String message = builder.toString();
        return message;
    }

    /**
     * @param testingDirectory
     * @return
     * @throws IllegalArgumentException
     * @throws IOException
     */
    public static String checkDirectory(String testingDirectory, boolean writable) throws IllegalArgumentException {

        File inDir = new File(testingDirectory);
        boolean failure = !inDir.exists() || !inDir.isDirectory() || inDir.isHidden() || !inDir.canRead();
        if (writable) {
            failure |= !inDir.canWrite();
        }
        if (failure) {
            String message = "Unable to create the mosaic\n" + "location is:" + testingDirectory + "\n"
                    + "location exists:" + inDir.exists() + "\n" + "location is a directory:" + inDir.isDirectory()
                    + "\n" + "location is writable:" + inDir.canWrite() + "\n" + "location is readable:"
                    + inDir.canRead() + "\n" + "location is hidden:" + inDir.isHidden() + "\n";
            LOGGER.severe(message);
            throw new IllegalArgumentException(message);
        }
        try {
            testingDirectory = inDir.getCanonicalPath();
        } catch (IOException e) {
            throw new IllegalArgumentException(e);
        }
        testingDirectory = FilenameUtils.normalize(testingDirectory);
        if (!testingDirectory.endsWith(File.separator))
            testingDirectory = testingDirectory + File.separator;
        // test to see if things are still good
        inDir = new File(testingDirectory);
        failure = !inDir.exists() || !inDir.isDirectory() || inDir.isHidden() || !inDir.canRead();
        if (writable) {
            failure |= !inDir.canWrite();
        }
        if (failure) {
            String message = "Unable to create the mosaic\n" + "location is:" + testingDirectory + "\n"
                    + "location exists:" + inDir.exists() + "\n" + "location is a directory:" + inDir.isDirectory()
                    + "\n" + "location is writable:" + inDir.canWrite() + "\n" + "location is readable:"
                    + inDir.canRead() + "\n" + "location is hidden:" + inDir.isHidden() + "\n";
            LOGGER.severe(message);
            throw new IllegalArgumentException(message);
        }
        return testingDirectory;
    }

    static boolean checkURLReadable(URL url) {
        try {
            url.openStream().close();
        } catch (Exception e) {
            return false;
        }
        return true;
    }

    public static final DataStoreFactorySpi SHAPE_SPI = new ShapefileDataStoreFactory();

    static final String DIRECT_KAKADU_PLUGIN = "it.geosolutions.imageio.plugins.jp2k.JP2KKakaduImageReader";

    public static final boolean DEFAULT_RECURSION_BEHAVIOR = true;

    /**
     * 
     * @param datastoreProperties
     * @return
     * @throws IOException
     */
    public static Map<String, Serializable> createDataStoreParamsFromPropertiesFile(final URL datastoreProperties)
            throws IOException {
        // read the properties file
        Properties properties = loadPropertiesFromURL(datastoreProperties);
        if (properties == null)
            return null;

        // SPI
        final String SPIClass = properties.getProperty("SPI");
        try {
            // create a datastore as instructed
            final DataStoreFactorySpi spi = (DataStoreFactorySpi) Class.forName(SPIClass).newInstance();
            return createDataStoreParamsFromPropertiesFile(properties, spi);
        } catch (ClassNotFoundException e) {
            final IOException ioe = new IOException();
            throw (IOException) ioe.initCause(e);
        } catch (InstantiationException e) {
            final IOException ioe = new IOException();
            throw (IOException) ioe.initCause(e);
        } catch (IllegalAccessException e) {
            final IOException ioe = new IOException();
            throw (IOException) ioe.initCause(e);
        }
    }

    /**
     * Store a sample image from which we can derive the default SM and CM
     * 
     * @param sampleImageFile
     *            where we should store the image
     * @param defaultSM
     *            the {@link SampleModel} for the sample image.
     * @param defaultCM
     *            the {@link ColorModel} for the sample image.
     * @throws IOException
     *             in case something bad occurs during writing.
     */
    public static void storeSampleImage(final File sampleImageFile, final SampleModel defaultSM,
            final ColorModel defaultCM) throws IOException {
        // create 1X1 image
        final SampleModel sm = defaultSM.createCompatibleSampleModel(1, 1);
        final WritableRaster raster = RasterFactory.createWritableRaster(sm, null);
        final BufferedImage sampleImage = new BufferedImage(defaultCM, raster, false, null);

        // serialize it
        OutputStream outStream = null;
        ObjectOutputStream ooStream = null;
        SerializableRenderedImage sri = null;
        try {
            outStream = new BufferedOutputStream(new FileOutputStream(sampleImageFile));
            ooStream = new ObjectOutputStream(outStream);
            sri = new SerializableRenderedImage(sampleImage, true);
            ooStream.writeObject(sri);
        } finally {
            try {
                if (ooStream != null)
                    ooStream.close();
            } catch (Throwable e) {
                IOUtils.closeQuietly(ooStream);
            }
            try {
                if (outStream != null)
                    outStream.close();
            } catch (Throwable e) {
                IOUtils.closeQuietly(outStream);
            }
            try {
                if (sri != null)
                    sri.dispose();
            } catch (Throwable e) {
            }
        }
    }

    /**
     * Load a sample image from which we can take the sample model and color
     * model to be used to fill holes in responses.
     * 
     * @param sampleImageFile
     *            the path to sample image.
     * @return a sample image from which we can take the sample model and color
     *         model to be used to fill holes in responses.
     */
    public static RenderedImage loadSampleImage(final File sampleImageFile) {
        // serialize it
        InputStream inStream = null;
        ObjectInputStream oiStream = null;
        try {

            // do we have the sample image??
            if (Utils.checkFileReadable(sampleImageFile)) {
                inStream = new BufferedInputStream(new FileInputStream(sampleImageFile));
                oiStream = new ObjectInputStream(inStream);

                // load the image
                return (RenderedImage) oiStream.readObject();

            } else {
                if (LOGGER.isLoggable(Level.WARNING))
                    LOGGER.warning("Unable to find sample image for path " + sampleImageFile);
                return null;
            }
        } catch (FileNotFoundException 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;
        } catch (ClassNotFoundException e) {
            if (LOGGER.isLoggable(Level.WARNING))
                LOGGER.log(Level.WARNING, e.getLocalizedMessage(), e);
            return null;
        } finally {
            try {
                if (inStream != null)
                    inStream.close();
            } catch (Throwable e) {

                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
            }
            try {
                if (oiStream != null)
                    oiStream.close();
            } catch (Throwable e) {

                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
            }
        }
    }

    /**
     * A transparent color for missing data.
     */
    static final Color TRANSPARENT = new Color(0, 0, 0, 0);

    final static Boolean IGNORE_FOOTPRINT = Boolean.getBoolean("org.geotools.footprint.ignore");

    public static final boolean DEFAULT_FOOTPRINT_MANAGEMENT = true;

    public static final boolean DEFAULT_CONFIGURATION_CACHING = false;

    public static Map<String, Serializable> createDataStoreParamsFromPropertiesFile(Properties properties,
            DataStoreFactorySpi spi) throws IOException {
        // 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)
                throw new IOException("Required parameter missing: " + p.toString());
        }

        return params;
    }

    static URL checkSource(Object source, Hints hints) {
        URL sourceURL = null;
        File sourceFile = null;

        //
        // Check source
        //
        // if it is a URL or a String let's try to see if we can get a file to
        // check if we have to build the index
        if (source instanceof File) {
            sourceFile = (File) source;
            sourceURL = DataUtilities.fileToURL(sourceFile);
        } else if (source instanceof URL) {
            sourceURL = (URL) source;
            if (sourceURL.getProtocol().equals("file")) {
                sourceFile = DataUtilities.urlToFile(sourceURL);
            }
        } else if (source instanceof String) {
            // is it a File?
            final String tempSource = (String) source;
            File tempFile = new File(tempSource);
            if (!tempFile.exists()) {
                // is it a URL
                try {
                    sourceURL = new URL(tempSource);
                    source = DataUtilities.urlToFile(sourceURL);
                } catch (MalformedURLException e) {
                    sourceURL = null;
                    source = null;
                }
            } else {
                sourceURL = DataUtilities.fileToURL(tempFile);

                // so that we can do our magic here below
                sourceFile = tempFile;
            }
        }

        // //
        //
        // at this point we have tried to convert the thing to a File as hard as
        // we could, let's see what we can do
        //
        // //
        if (sourceFile != null) {
            if (!sourceFile.isDirectory())
                // real file, can only be a shapefile at this stage or a
                // datastore.properties file
                sourceURL = DataUtilities.fileToURL((File) sourceFile);
            else {
                // it's a DIRECTORY, let's look for a possible properties files
                // that we want to load
                final String locationPath = sourceFile.getAbsolutePath();
                final String defaultIndexName = FilenameUtils.getName(locationPath);
                boolean datastoreFound = false;
                boolean buildMosaic = false;

                //
                // do we have a datastore properties file? It will preempt on
                // the shapefile
                //
                File dataStoreProperties = new File(locationPath, "datastore.properties");

                // this can be used to look for properties files that do NOT
                // define a datastore
                final File[] properties = sourceFile.listFiles((FilenameFilter) FileFilterUtils.and(
                        FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("datastore.properties")),
                        FileFilterUtils.makeFileOnly(FileFilterUtils.suffixFileFilter(".properties"))));

                // do we have a valid datastore + mosaic properties pair?
                if (Utils.checkFileReadable(dataStoreProperties)) {
                    // we have a datastore.properties file
                    datastoreFound = true;

                    // check the first valid mosaic properties
                    boolean found = false;
                    for (File propFile : properties)
                        if (Utils.checkFileReadable(propFile)) {
                            // load it
                            if (null != Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile), "location")) {
                                found = true;
                                break;
                            }
                        }

                    // we did not find any good candidate for mosaic.properties
                    // file, this will signal it
                    if (!found)
                        buildMosaic = true;

                } else {
                    // we did not find any good candidate for mosaic.properties
                    // file, this will signal it
                    buildMosaic = true;
                    datastoreFound = false;
                }

                //
                // now let's try with shapefile and properties couple
                //
                File shapeFile = null;
                if (!datastoreFound) {
                    for (File propFile : properties) {

                        // load properties
                        if (null == Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile),
                                Utils.DEFAULT_LOCATION_ATTRIBUTE))
                            continue;

                        // look for a couple shapefile, mosaic properties file
                        shapeFile = new File(locationPath, FilenameUtils.getBaseName(propFile.getName()) + ".shp");
                        if (!Utils.checkFileReadable(shapeFile) && Utils.checkFileReadable(propFile))
                            buildMosaic = true;
                        else {
                            buildMosaic = false;
                            break;
                        }
                    }

                }

                // did we find anything? If no, we try to build a new mosaic
                if (buildMosaic) {
                    ////
                    //
                    // Creating a new mosaic
                    //
                    ////
                    // try to build a mosaic inside this directory and see what
                    // happens

                    // preliminar checks
                    final File mosaicDirectory = new File(locationPath);
                    if (!mosaicDirectory.exists() || mosaicDirectory.isFile() || !mosaicDirectory.canWrite()) {
                        if (LOGGER.isLoggable(Level.SEVERE)) {
                            LOGGER.log(Level.SEVERE,
                                    "Unable to create the mosaic, check the location:\n" + "location is:"
                                            + locationPath + "\n" + "location exists:" + mosaicDirectory.exists()
                                            + "\n" + "location is a directory:" + mosaicDirectory.isDirectory()
                                            + "\n" + "location is writable:" + mosaicDirectory.canWrite() + "\n"
                                            + "location is readable:" + mosaicDirectory.canRead() + "\n"
                                            + "location is hidden:" + mosaicDirectory.isHidden() + "\n");
                        }
                        return null;
                    }

                    // actual creation
                    createMosaic(locationPath, defaultIndexName, DEFAULT_WILCARD, DEFAULT_PATH_BEHAVIOR, hints);

                    // check that the mosaic properties file was created
                    final File propertiesFile = new File(locationPath, defaultIndexName + ".properties");
                    if (!Utils.checkFileReadable(propertiesFile)) {
                        // retrieve a null so that we shows that a problem occurred
                        sourceURL = null;
                        return sourceURL;
                    }

                    // check that the shapefile was correctly created in case it
                    // was needed
                    if (!datastoreFound) {
                        shapeFile = new File(locationPath, defaultIndexName + ".shp");

                        if (!Utils.checkFileReadable(shapeFile))
                            sourceURL = null;
                        else
                            // now set the new source and proceed
                            sourceURL = DataUtilities.fileToURL(shapeFile);
                    } else {
                        dataStoreProperties = new File(locationPath, "datastore.properties");

                        // datastore.properties as the source
                        if (!Utils.checkFileReadable(dataStoreProperties)) {
                            sourceURL = null;
                        } else {
                            sourceURL = DataUtilities.fileToURL(dataStoreProperties);
                        }
                    }

                } else
                    // now set the new source and proceed
                    sourceURL = datastoreFound ? DataUtilities.fileToURL(dataStoreProperties)
                            : DataUtilities.fileToURL(shapeFile);

            }
        } else {
            // SK: We don't set SourceURL to null now, just because it doesn't
            // point to a file
            // sourceURL=null;
        }
        return sourceURL;
    }

    static final double SAMEBBOX_THRESHOLD_FACTOR = 20;

    static final double AFFINE_IDENTITY_EPS = 1E-6;

    public static final boolean DEFAULT_COLOR_EXPANSION_BEHAVIOR = false;

    public static final TimeZone UTC_TIME_ZONE = TimeZone.getTimeZone("UTC");

    static final String DESCENDING_ORDER_IDENTIFIER = " D"; //SortOrder.DESCENDING.identifier();

    static final String ASCENDING_ORDER_IDENTIFIER = " A"; //SortOrder.ASCENDING.identifier();

    /**
     * Private constructor to initialize the ehCache instance.
     * It can be configured through a Bean.
     * @param ehcache
     */
    private Utils(Cache ehcache) {
        Utils.ehcache = ehcache;
    }

    /**
     * Setup a {@link Histogram} object by deserializing  
     * a file representing a serialized Histogram.
     * 
     * @param file
     * @return the deserialized histogram.
     */
    static Histogram getHistogram(final String file) {
        Utilities.ensureNonNull("file", file);
        Histogram histogram = null;

        // Firstly: check if the histogram have been already
        // deserialized and it is available in cache
        if (ehcache != null && ehcache.isKeyInCache(file)) {
            if (ehcache.isElementInMemory(file)) {
                final Element element = ehcache.get(file);
                if (element != null) {
                    final Serializable value = element.getValue();
                    if (value != null && value instanceof Histogram) {
                        histogram = (Histogram) value;
                        return histogram;
                    }
                }
            }
        }

        // No histogram in cache. Deserializing...
        if (histogram == null) {
            FileInputStream fileStream = null;
            ObjectInputStream objectStream = null;
            try {

                fileStream = new FileInputStream(file);
                objectStream = new ObjectInputStream(fileStream);
                histogram = (Histogram) objectStream.readObject();
                if (ehcache != null) {
                    ehcache.put(new Element(file, histogram));
                }
            } catch (FileNotFoundException e) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage());
                }
            } catch (IOException e) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage());
                }
            } catch (ClassNotFoundException e) {
                if (LOGGER.isLoggable(Level.FINE)) {
                    LOGGER.fine("Unable to parse Histogram:" + e.getLocalizedMessage());
                }
            } finally {
                if (objectStream != null) {
                    IOUtils.closeQuietly(objectStream);
                }
                if (fileStream != null) {
                    IOUtils.closeQuietly(fileStream);
                }
            }
        }
        return histogram;
    }

    /**
     * Check if the provided granule's footprint covers the same area of the granule's bbox.
     * @param granuleFootprint the granule Footprint
     * @param granuleBBOX the granule bbox
     * @return {@code true} in case the footprint isn't covering the full granule's bbox. 
     */
    static boolean areaIsDifferent(final Geometry granuleFootprint, final AffineTransform baseGridToWorld,
            final ReferencedEnvelope granuleBBOX) {

        // // 
        //
        // First preliminar check:
        // check if the footprint's bbox corners are the same of the granule's bbox
        // (Using a threshold)
        //
        // //
        final Envelope envelope = granuleFootprint.getEnvelope().getEnvelopeInternal();
        double deltaMinX = Math.abs(envelope.getMinX() - granuleBBOX.getMinX());
        double deltaMinY = Math.abs(envelope.getMinY() - granuleBBOX.getMinY());
        double deltaMaxX = Math.abs(envelope.getMaxX() - granuleBBOX.getMaxX());
        double deltaMaxY = Math.abs(envelope.getMaxY() - granuleBBOX.getMaxY());
        final double resX = XAffineTransform.getScaleX0(baseGridToWorld);
        final double resY = XAffineTransform.getScaleY0(baseGridToWorld);
        final double toleranceX = resX / Utils.SAMEBBOX_THRESHOLD_FACTOR;
        final double toleranceY = resY / Utils.SAMEBBOX_THRESHOLD_FACTOR;

        // Taking note of the area of a single cell
        final double cellArea = resX * resY;

        if (deltaMinX > toleranceX || deltaMaxX > toleranceX || deltaMinY > toleranceY || deltaMaxY > toleranceY) {
            // delta exceed tolerance. Area is not the same           
            return true;
        }

        // //
        //
        // Second check:
        // Here, the footprint's bbox and the granule's bbox are equal.
        // However this is not enough:
        // - suppose the footprint is a diamond
        // - Create a rectangle by circumscribing the diamond
        // - If this rectangle match with the granule's bbox, this doesn't imply
        // that the diamond covers the same area of the bbox.
        // Therefore, we need to compute the area and compare them.
        //
        // //
        final double footprintArea = granuleFootprint.getArea();
        //final double bboxArea = granuleBBOX.getArea();
        final double bboxArea = granuleBBOX.getHeight() * granuleBBOX.getWidth();

        // If 2 areas are different more than the cellArea, then they are not the same area
        if (Math.abs(footprintArea - bboxArea) > cellArea)
            return true;
        return false;
    }

    /**
     * Checks if the Shape equates to a Rectangle, if it does it performs a conversion, otherwise
     * returns null
     * @param shape
     * @return
     */
    static Rectangle toRectangle(Shape shape) {
        if (shape instanceof Rectangle) {
            return (Rectangle) shape;
        }

        if (shape == null) {
            return null;
        }

        // check if it's equivalent to a rectangle
        PathIterator iter = shape.getPathIterator(new AffineTransform());
        double[] coords = new double[2];

        // not enough points?
        if (iter.isDone()) {
            return null;
        }

        // get the first and init the data structures
        iter.next();
        int action = iter.currentSegment(coords);
        if (action != PathIterator.SEG_MOVETO && action != PathIterator.SEG_LINETO) {
            return null;
        }
        double minx = coords[0];
        double miny = coords[1];
        double maxx = minx;
        double maxy = miny;
        double prevx = minx;
        double prevy = miny;
        int i = 0;

        // at most 4 steps, if more it's not a strict rectangle
        for (; i < 4 && !iter.isDone(); i++) {
            iter.next();
            action = iter.currentSegment(coords);

            if (action == PathIterator.SEG_CLOSE) {
                break;
            }
            if (action != PathIterator.SEG_LINETO) {
                return null;
            }

            // check orthogonal step (x does not change and y does, or vice versa)
            double x = coords[0];
            double y = coords[1];
            if (!(prevx == x && prevy != y) && !(prevx != x && prevy == y)) {
                return null;
            }

            // update mins and maxes
            if (x < minx) {
                minx = x;
            } else if (x > maxx) {
                maxx = x;
            }
            if (y < miny) {
                miny = y;
            } else if (y > maxy) {
                maxy = y;
            }

            // keep track of prev step
            prevx = x;
            prevy = y;
        }

        // if more than 4 other points it's not a standard rectangle
        iter.next();
        if (!iter.isDone() || i != 3) {
            return null;
        }

        // turn it into a rectangle
        return new Rectangle2D.Double(minx, miny, maxx - minx, maxy - miny).getBounds();
    }
}