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

Java tutorial

Introduction

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

Source

/*
 *    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.Rectangle;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.spi.ImageReaderSpi;

import org.apache.commons.io.filefilter.FileFilterUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.io.AbstractGridCoverage2DReader;
import org.geotools.coverage.grid.io.AbstractGridFormat;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.factory.Hints;
import org.geotools.feature.visitor.CalcResult;
import org.geotools.feature.visitor.FeatureCalc;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalog;
import org.geotools.gce.imagemosaic.catalog.GranuleCatalogFactory;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.parameter.DefaultParameterDescriptor;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.resources.coverage.FeatureUtilities;
import org.geotools.util.Range;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.Format;
import org.opengis.coverage.grid.GridCoverageReader;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.filter.Filter;
import org.opengis.geometry.BoundingBox;
import org.opengis.metadata.Identifier;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.parameter.ParameterDescriptor;
import org.opengis.parameter.ParameterValue;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.MathTransform;

/**
 * This reader is responsible for providing access to mosaic of georeferenced
 * images. Citing JAI documentation:
 * 
 * The "Mosaic" operation creates a mosaic of two or more source images. This
 * operation could be used for example to assemble a set of overlapping
 * geospatially rectified images into a contiguous image. It could also be used
 * to create a montage of photographs such as a panorama.
 * 
 * All source images are assumed to have been geometrically mapped into a common
 * coordinate space. The origin (minX, minY) of each image is therefore taken to
 * represent the location of the respective image in the common coordinate
 * system of the source images. This coordinate space will also be that of the
 * destination image.
 * 
 * All source images must have the same data type and sample size for all bands
 * and have the same number of bands as color components. The destination will
 * have the same data type, sample size, and number of bands and color
 * components as the sources.
 * 
 * 
 * @author Simone Giannecchini, GeoSolutions S.A.S
 * @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
 * @since 2.3
 * 
 *
 *
 * @source $URL$
 */
@SuppressWarnings("rawtypes")
public class ImageMosaicReader extends AbstractGridCoverage2DReader implements GridCoverageReader {

    /**
     * {@link DomainDescriptor} describe a single domain in terms of name and {@link ParameterDescriptor} 
     * that can be used to filter values during a read operation.
     * 
     * <p> Notice that there is no caching of values for the domain itself right now.
     * 
     * <p>
     * The domain must have unique identifiers.
     * 
     * @author Simone Giannecchini, GeoSolutions SAS
     *
     */
    class DomainDescriptor {

        static final String DOMAIN_SUFFIX = "_DOMAIN";

        static final String HAS_PREFIX = "HAS_";

        /** Unique identifier for this domain.*/
        private final String identifier;

        /** propertyName for this domain that tells me which Property from the underlying catalog provides values for it.*/
        private final String propertyName;

        /** The {@link ParameterDescriptor} that can be used to filter on this domain during a read operation.*/
        private final DefaultParameterDescriptor<List> domainParameterDescriptor;

        /**
         * @return the identifier
         */
        private String getIdentifier() {
            return identifier;
        }

        /**
         * @return the propertyName
         */
        private String getPropertyName() {
            return propertyName;
        }

        /**
         * @return the domainaParameterDescriptor
         */
        private DefaultParameterDescriptor<List> getDomainaParameterDescriptor() {
            return domainParameterDescriptor;
        }

        private DomainDescriptor(String identifier, String propertyName) {
            this.identifier = identifier;
            this.propertyName = propertyName;
            final String name = identifier.toUpperCase();
            this.domainParameterDescriptor = DefaultParameterDescriptor.create(name,
                    "Additional " + identifier + " domain", List.class, null, false);
        }

        /**
         * Extract the time domain extrema.
         * 
         * @param extrema a {@link String} either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM.
         * 
         * @return either TIME_DOMAIN_MAXIMUM or TIME_DOMAIN_MINIMUM as a {@link String}.
         * TODO use num for extrema
         */
        private String getExtrema(String extrema) {
            try {
                final FeatureCalc visitor = rasterManager.createExtremaQuery(extrema, propertyName);

                // check result
                CalcResult tempRes = visitor.getResult();
                if (tempRes == null) {
                    throw new IllegalStateException("Unable to compute extrema value:" + extrema);
                }
                final Object result = tempRes.getValue();
                if (result == null) {
                    throw new IllegalStateException("Unable to compute extrema value:" + extrema);
                }
                return ConvertersHack.convert(result, String.class);
            } catch (IOException e) {
                if (LOGGER.isLoggable(Level.WARNING))
                    LOGGER.log(Level.WARNING, "Unable to compute extrema for TIME_DOMAIN", e);
                return null;
            }
        }

        /**
         * Retrieves the values for this domain
         * @return
         */
        private String getValues() {
            try {
                final Set result = rasterManager.extractDomain(propertyName);
                // check result
                if (result.size() <= 0) {
                    return "";
                }

                final StringBuilder buff = new StringBuilder();
                for (Iterator it = result.iterator(); it.hasNext();) {
                    buff.append(ConvertersHack.convert(it.next(), String.class));
                    if (it.hasNext()) {
                        buff.append(",");
                    }
                }
                return buff.toString();

            } catch (IOException e) {
                if (LOGGER.isLoggable(Level.WARNING))
                    LOGGER.log(Level.WARNING, "Unable to parse attribute: " + identifier, e);
                return "";
            }
        }
    }

    /**
     * An {@link DomainManager} class which allows to deal with additional domains
     * (if any) defined inside the mosaic. It provides DOMAIN_ALIAS <--to--> original attribute mapping
     * capabilities, metadata retrieval, filter creation, and domain support check
     * 
     * @author Daniele Romagnoli, GeoSolutions S.a.S.
     * TODO move to {@link RasterManager}
     */
    class DomainManager {

        private final Map<String, DomainDescriptor> domainsMap = new HashMap<String, ImageMosaicReader.DomainDescriptor>();

        DomainManager(Map<String, String> additionalDomainAttributes, SimpleFeatureType simpleFeatureType) {
            Utilities.ensureNonNull("additionalDomainAttributes", additionalDomainAttributes);
            Utilities.ensureNonNull("simpleFeatureType", simpleFeatureType);
            init(additionalDomainAttributes, simpleFeatureType);
        }

        /**
         * @param additionalDomainAttributes
         * @param simpleFeatureType
         * @throws IllegalArgumentException
         */
        private void init(Map<String, String> additionalDomainAttributes, SimpleFeatureType simpleFeatureType)
                throws IllegalArgumentException {
            for (java.util.Map.Entry<String, String> entry : additionalDomainAttributes.entrySet()) {

                final String domainName = entry.getKey();
                String propertyName = entry.getValue();
                // is the name equals to the propertyname?
                try {
                    if (simpleFeatureType.getDescriptor(propertyName) != null) {
                        // add
                        addDomain(domainName, propertyName);

                        // continue
                        continue;
                    }

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

                // ok why we don't have it? Maybe shapefile name truncation?
                if (propertyName.length() > 10) {
                    // hakc for shapes
                    propertyName = propertyName.substring(0, 10);
                    // alias in provided type
                    try {
                        if (simpleFeatureType.getDescriptor(propertyName) != null) {
                            // add
                            addDomain(domainName, propertyName);

                            // continue
                            continue;
                        }

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

                // if I got here, we are in trouble. No way to add this param
                throw new IllegalArgumentException("Unable to add this domain:" + domainName + "-" + propertyName);

            }
        }

        /**
         * build an AdditionalDomainManager on top of the provided additionalDomainAttributes 
         * (a comma separated list of attribute names).
         *
         * @param additionalDomainAttributes
         * @param simpleFeatureType 
         */
        DomainManager(String additionalDomainAttributes, SimpleFeatureType simpleFeatureType) {
            Utilities.ensureNonNull("additionalDomainAttributes", additionalDomainAttributes);
            Utilities.ensureNonNull("simpleFeatureType", simpleFeatureType);

            final Map<String, String> domainPairs = new HashMap<String, String>();

            // split, looking for multiple values
            final String[] additionalDomainsNames = additionalDomainAttributes.split(",");
            if (additionalDomainsNames.length <= 0) {
                throw new IllegalArgumentException("Number of Domains should be > 0");
            }

            // add al the provided domain
            for (String domainName : additionalDomainsNames) {
                domainPairs.put(domainName, domainName);
            }
            init(domainPairs, simpleFeatureType);

        }

        /**
         * Add a domain to the manager
         * @param domain the name of the domain
         * @param propertyName 
         */
        private void addDomain(String name, String propertyName) {
            Utilities.ensureNonNull("name", name);
            Utilities.ensureNonNull("propertyName", propertyName);

            // === checks
            // existing!
            if (domainsMap.containsKey(name)) {
                throw new IllegalArgumentException("Trying to add a domain with an existing name" + name);
            }

            // ad with uppercase and with suffix, the parameter that describes it will match this
            final String upperCase = name.toUpperCase();
            domainsMap.put(upperCase + DomainDescriptor.DOMAIN_SUFFIX, new DomainDescriptor(name, propertyName));

        }

        /**
         * Check whether a specific parameter (identified by the {@link Identifier} name) is supported by 
         * this manager (and therefore, by the reader).
         * @param name
         * @return
         */
        public boolean isParameterSupported(final Identifier name) {
            if (!domainsMap.isEmpty()) {
                for (DomainDescriptor domain : domainsMap.values()) {
                    final ReferenceIdentifier nameLoc = domain.getDomainaParameterDescriptor().getName();
                    if (nameLoc.equals(name)) {
                        return true;
                    }
                }
            }
            return false;
        }

        /**
         * Setup the List of metadataNames for this additional domains manager
         *
         * @return
         */
        public List<String> getMetadataNames() {
            final List<String> metadataNames = new ArrayList<String>();
            if (!domainsMap.isEmpty()) {
                for (DomainDescriptor domain : domainsMap.values()) {
                    String domainName = domain.getIdentifier().toUpperCase();
                    metadataNames.add(domainName + DomainDescriptor.DOMAIN_SUFFIX);
                    metadataNames.add(DomainDescriptor.HAS_PREFIX + domainName + DomainDescriptor.DOMAIN_SUFFIX);
                }
            }
            return metadataNames;
        }

        /**
         * Return the value of a specific metadata by parsing the requested name as a Domain Name
         * @param name
         * @return
         */
        public String getMetadataValue(String name) {
            Utilities.ensureNonNull("name", name);

            String value = null;
            if (domainsMap.size() > 0) {
                // is a domain?
                if (domainsMap.containsKey(name)) {
                    final DomainDescriptor domainDescriptor = domainsMap.get(name);
                    value = domainDescriptor.getValues();
                } else {
                    // is a simple Has domain query?
                    if (name.startsWith(DomainDescriptor.HAS_PREFIX)) {
                        final String substring = name.substring(DomainDescriptor.HAS_PREFIX.length(),
                                name.length());
                        if (domainsMap.containsKey(substring)) {
                            return Boolean.toString(Boolean.TRUE);
                        } else {
                            return Boolean.toString(Boolean.FALSE);
                        }
                    } else {
                        // MINUM or MAXIMUM
                        if (name.endsWith("MINIMUM") || name.endsWith("MAXIMUM")) {
                            return domainsMap.get(name.substring(0, name.lastIndexOf("_"))).getExtrema(name);
                        }
                    }
                }
            }
            return value;
        }

        /**
         * Setup a Filter on top of the specified domainRequest which is in the form "key=value"
         * 
         * @param domain
         * @param values
         * @return
         */
        public Filter createFilter(String domain, List values) {
            // === checks
            if (domain == null || domain.isEmpty()) {
                throw new IllegalArgumentException("Null domain requested");
            }
            if (values == null || values.isEmpty()) {
                throw new IllegalArgumentException("Null domain values provided");
            }
            if (domainsMap.isEmpty() || !domainsMap.containsKey(domain)) {
                throw new IllegalArgumentException("requested domain is not supported by this mosaic: " + domain);
            }

            // get the property name
            DomainDescriptor domainDescriptor = domainsMap.get(domain);
            final String propertyName = domainDescriptor.getPropertyName();

            // === create the filter
            // loop values and AND them
            final int size = values.size();
            final List<Filter> filters = new ArrayList<Filter>();
            for (int i = 0; i < size; i++) {
                // checks
                final Object value = values.get(i);
                if (value == null) {
                    if (LOGGER.isLoggable(Level.INFO)) {
                        LOGGER.info("Ignoring null date for the filter:" + domain);
                    }
                    continue;
                }
                if (value instanceof Range) {
                    // RANGE                        
                    final Range range = (Range) value;
                    filters.add(FeatureUtilities.DEFAULT_FILTER_FACTORY.and(
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.lessOrEqual(
                                    FeatureUtilities.DEFAULT_FILTER_FACTORY.property(propertyName),
                                    FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMaxValue())),
                            FeatureUtilities.DEFAULT_FILTER_FACTORY.greaterOrEqual(
                                    FeatureUtilities.DEFAULT_FILTER_FACTORY.property(propertyName),
                                    FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(range.getMinValue()))));
                    continue;
                }
                // SINGLE value
                filters.add(FeatureUtilities.DEFAULT_FILTER_FACTORY.equal(
                        FeatureUtilities.DEFAULT_FILTER_FACTORY.property(propertyName),
                        FeatureUtilities.DEFAULT_FILTER_FACTORY.literal(value), true));
            }
            return FeatureUtilities.DEFAULT_FILTER_FACTORY.or(filters);

        }

        /** 
          * Return the set of dynamic parameterDescriptors (the ones related to domains) for this reader 
          * @return
          */
        public Set<ParameterDescriptor<List>> getDynamicParameters() {
            Set<ParameterDescriptor<List>> dynamicParameters = new HashSet<ParameterDescriptor<List>>();
            if (!domainsMap.isEmpty()) {
                for (DomainDescriptor domain : domainsMap.values()) {
                    dynamicParameters.add(domain.getDomainaParameterDescriptor());
                }
            }
            // return
            return dynamicParameters;
        }

    }

    /** Logger. */
    private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(ImageMosaicReader.class);

    /**
     * The source {@link URL} pointing to the index shapefile for this
     * {@link ImageMosaicReader}.
     */
    URL sourceURL;

    boolean expandMe;

    PathType pathType;

    ExecutorService multiThreadedLoader = null;

    String locationAttributeName = Utils.DEFAULT_LOCATION_ATTRIBUTE;

    RasterManager rasterManager;

    /** The inner {@link DomainManager} instance which allows to manage custom dimensions */
    DomainManager domainsManager;

    int maxAllowedTiles = ImageMosaicFormat.MAX_ALLOWED_TILES.getDefaultValue();

    /** The suggested SPI to avoid SPI lookup*/
    ImageReaderSpi suggestedSPI;

    GranuleCatalog catalog;

    boolean cachingIndex;

    boolean imposedBBox;

    boolean heterogeneousGranules;

    String typeName;

    DomainManager timeDomainManager;

    DomainManager elevationDomainManager;

    /**
     * Constructor.
     * 
     * @param source
     *            The source object.
     * @throws IOException
     * @throws UnsupportedEncodingException
     * 
     */
    public ImageMosaicReader(Object source, Hints uHints) throws IOException {
        super(source, uHints);

        //
        // try to extract a multithreaded loader if available
        //
        if (this.hints.containsKey(Hints.EXECUTOR_SERVICE)) {
            final Object executor = uHints.get(Hints.EXECUTOR_SERVICE);
            if (executor != null && executor instanceof ExecutorService) {
                multiThreadedLoader = (ExecutorService) executor;
                if (LOGGER.isLoggable(Level.FINE)) {
                    if (multiThreadedLoader instanceof ThreadPoolExecutor) {
                        final ThreadPoolExecutor tpe = (ThreadPoolExecutor) multiThreadedLoader;
                        LOGGER.fine("Using ThreadPoolExecutor with the following settings: " + "core pool size = "
                                + tpe.getCorePoolSize() + "\nmax pool size = " + tpe.getMaximumPoolSize()
                                + "\nkeep alive time " + tpe.getKeepAliveTime(TimeUnit.MILLISECONDS));
                    }
                }
            }
        }
        if (this.hints.containsKey(Hints.MAX_ALLOWED_TILES))
            this.maxAllowedTiles = ((Integer) this.hints.get(Hints.MAX_ALLOWED_TILES));

        //
        // Check source
        //
        if (source instanceof ImageMosaicDescriptor) {
            initReaderFromDescriptor((ImageMosaicDescriptor) source, uHints);
        } else {
            try {
                initReaderFromURL(source, uHints);
            } catch (Exception e) {
                throw new DataSourceException(e);
            }
        }
    }

    /**
     * Init this {@link ImageMosaicReader} using the provided {@link ImageMosaicDescriptor} as source. 
     * @param source
     * @param uHints
     * @throws DataSourceException
     */
    private void initReaderFromDescriptor(final ImageMosaicDescriptor source, final Hints uHints)
            throws IOException {
        Utilities.ensureNonNull("source", source);
        final MosaicConfigurationBean configuration = source.getConfiguration();
        if (configuration == null) {
            throw new DataSourceException(
                    "Unable to create reader for this mosaic since we could not parse the configuration.");
        }
        extractProperties(configuration);
        catalog = source.getCatalog();
        if (catalog == null) {
            throw new DataSourceException(
                    "Unable to create reader for this mosaic since the inner catalog is null.");
        }

        final SimpleFeatureType schema = catalog.getType();
        if (schema == null) {
            throw new DataSourceException(
                    "Unable to create reader for this mosaic since the inner catalog schema is null.");
        }

        if (configuration.getAdditionalDomainAttributes() != null) {
            domainsManager = new DomainManager(configuration.getAdditionalDomainAttributes(), schema);
        }

        // time attribute
        if (configuration.getTimeAttribute() != null) {
            final HashMap<String, String> init = new HashMap<String, String>();
            init.put("TIME", configuration.getTimeAttribute());
            timeDomainManager = new DomainManager(init, schema);
        }
        // elevation attribute
        if (configuration.getElevationAttribute() != null) {
            final HashMap<String, String> init = new HashMap<String, String>();
            init.put("ELEVATION", configuration.getElevationAttribute());
            elevationDomainManager = new DomainManager(init, schema);
        }

        // grid geometry
        setGridGeometry();

        // raster manager
        rasterManager = new RasterManager(this);
        rasterManager.defaultSM = configuration.getSampleModel();

    }

    /**
     * Init this {@link ImageMosaicReader} using the provided object as a source referring to an {@link URL}. 
     * 
     * @param source
     * @param uHints
     * @throws DataSourceException
     */
    private void initReaderFromURL(final Object source, final Hints hints) throws Exception {
        this.sourceURL = Utils.checkSource(source, hints);
        if (this.sourceURL == null)
            throw new DataSourceException(
                    "This plugin accepts File, URL or String. The string may describe a File or an URL");

        //
        // Load properties file with information about levels and envelope
        //
        MosaicConfigurationBean configuration = Utils.loadMosaicProperties(sourceURL, this.locationAttributeName);
        if (configuration == null) {
            //
            // 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.and(
                    FileFilterUtils.notFileFilter(FileFilterUtils.nameFileFilter("indexer.properties")),
                    FileFilterUtils.and(
                            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) {
                    configuration = Utils.loadMosaicProperties(DataUtilities.fileToURL(propFile),
                            this.locationAttributeName);
                }

        }
        if (configuration == null)
            throw new DataSourceException(
                    "Unable to create reader for this mosaic since we could not parse the configuration.");

        // now load the configuration and extract properties from there
        extractProperties(configuration);

        //location attribute override
        if (this.hints.containsKey(Hints.MOSAIC_LOCATION_ATTRIBUTE)) {
            this.locationAttributeName = ((String) this.hints.get(Hints.MOSAIC_LOCATION_ATTRIBUTE));
        }

        //
        // Load tiles informations, especially the bounds, which will be
        // reused
        //
        try {
            // create the index
            catalog = GranuleCatalogFactory.createGranuleCatalog(sourceURL, configuration);
            // error
            if (catalog == null) {
                throw new DataSourceException("Unable to create index for this URL " + sourceURL);
            }
            final SimpleFeatureType type = catalog.getType();
            if (type == null) {
                throw new IllegalArgumentException(
                        "Problems when opening the index, no typenames for the schema are defined");
            }

            // additional domain properties
            SimpleFeatureType schema = catalog.getType();
            if (configuration.getAdditionalDomainAttributes() != null) {
                domainsManager = new DomainManager(configuration.getAdditionalDomainAttributes(), schema);
            }
            // time attribute
            if (configuration.getTimeAttribute() != null) {
                final HashMap<String, String> init = new HashMap<String, String>();
                init.put("TIME", configuration.getTimeAttribute());
                timeDomainManager = new DomainManager(init, schema);
            }
            // elevation attribute
            if (configuration.getElevationAttribute() != null) {
                final HashMap<String, String> init = new HashMap<String, String>();
                init.put("ELEVATION", configuration.getElevationAttribute());
                elevationDomainManager = new DomainManager(init, schema);
            }

            // everything is fine
            if (LOGGER.isLoggable(Level.FINE))
                LOGGER.fine("Connected mosaic reader to its index " + sourceURL.toString());

            setGridGeometry(configuration.getEnvelope());

            //
            // get the crs if able to
            //
            final Object tempCRS = this.hints.get(Hints.DEFAULT_COORDINATE_REFERENCE_SYSTEM);
            if (tempCRS != null) {
                this.crs = (CoordinateReferenceSystem) tempCRS;
                LOGGER.log(Level.WARNING, "Using forced coordinate reference system ");
            } else {
                final CoordinateReferenceSystem tempcrs = type.getGeometryDescriptor()
                        .getCoordinateReferenceSystem();
                if (tempcrs == null) {
                    // use the default crs
                    crs = AbstractGridFormat.getDefaultCRS();
                    LOGGER.log(Level.WARNING, "Unable to find a CRS for this coverage, using a default one");
                } else
                    crs = tempcrs;
            }

            //
            // perform checks on location attribute name
            //
            if (this.locationAttributeName == null) {
                throw new DataSourceException("The provided name for the location attribute is invalid.");
            } else {
                if (type.getDescriptor(this.locationAttributeName) == null) {
                    // ORACLE fix
                    this.locationAttributeName = this.locationAttributeName.toUpperCase();

                    // try again with uppercase
                    if (type.getDescriptor(this.locationAttributeName) == null) {
                        throw new DataSourceException("The provided name for the location attribute is invalid.");
                    }
                }
            }

            // creating the raster manager
            rasterManager = new RasterManager(this);
        } catch (Throwable e) {
            try {
                if (catalog != null) {
                    catalog.dispose();
                }
            } catch (Throwable e1) {
                if (LOGGER.isLoggable(Level.FINEST)) {
                    LOGGER.log(Level.FINEST, e1.getLocalizedMessage(), e1);
                }
            } finally {
                catalog = null;
            }

            // dispose raster manager as well
            try {
                if (rasterManager != null)
                    rasterManager.dispose();
            } catch (Throwable e1) {
                if (LOGGER.isLoggable(Level.FINEST))
                    LOGGER.log(Level.FINEST, e1.getLocalizedMessage(), e1);
            } finally {
                rasterManager = null;
            }

            // rethrow
            throw new DataSourceException(e);
        }

    }

    private void setGridGeometry(ReferencedEnvelope envelope) {
        Utilities.ensureNonNull("index", catalog);
        //
        // save the bbox and prepare other info
        //
        final BoundingBox bounds = catalog.getBounds();
        if (bounds.isEmpty()) {
            throw new IllegalArgumentException("Cannot create a mosaic out of an empty index");
        }

        // we might have an imposed bbox
        this.crs = bounds.getCoordinateReferenceSystem();
        if (envelope == null)
            this.originalEnvelope = new GeneralEnvelope(bounds);
        else {
            this.originalEnvelope = new GeneralEnvelope(envelope);
            this.originalEnvelope.setCoordinateReferenceSystem(crs);
        }

        // original gridrange (estimated). I am using the floor here in order to make sure
        // we always stays inside the real area that we have for the granule
        originalGridRange = new GridEnvelope2D(new Rectangle((int) (originalEnvelope.getSpan(0) / highestRes[0]),
                (int) (originalEnvelope.getSpan(1) / highestRes[1])));
        raster2Model = new AffineTransform2D(highestRes[0], 0, 0, -highestRes[1],
                originalEnvelope.getLowerCorner().getOrdinate(0) + 0.5 * highestRes[0],
                originalEnvelope.getUpperCorner().getOrdinate(1) - 0.5 * highestRes[1]);

    }

    private void setGridGeometry() {
        setGridGeometry(null);
    }

    private void extractProperties(final MosaicConfigurationBean configuration) throws IOException {

        // resolutions levels
        numOverviews = configuration.getLevelsNum() - 1;
        final double[][] resolutions = configuration.getLevels();
        overViewResolutions = numOverviews >= 1 ? new double[numOverviews][2] : null;
        highestRes = new double[2];
        highestRes[0] = resolutions[0][0];
        highestRes[1] = resolutions[0][1];

        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine(new StringBuilder("Highest res ").append(highestRes[0]).append(" ").append(highestRes[1])
                    .toString());

        if (numOverviews > 0) {
            for (int i = 0; i < numOverviews; i++) {
                overViewResolutions[i][0] = resolutions[i + 1][0];
                overViewResolutions[i][1] = resolutions[i + 1][1];
            }
        }

        // name
        coverageName = configuration.getName();

        // need a color expansion?
        // this is a newly added property we have to be ready to the case where
        // we do not find it.
        expandMe = configuration.isExpandToRGB();

        // do we have heterogenous granules
        heterogeneousGranules = configuration.isHeterogeneous();

        // absolute or relative path
        pathType = configuration.isAbsolutePath() ? PathType.ABSOLUTE : PathType.RELATIVE;

        //
        // location attribute
        //
        locationAttributeName = configuration.getLocationAttribute();

        // suggested SPI
        final String suggestedSPIClass = configuration.getSuggestedSPI();
        if (suggestedSPIClass != null) {
            try {
                final Class<?> clazz = Class.forName(suggestedSPIClass);
                if (clazz.newInstance() instanceof ImageReaderSpi)
                    suggestedSPI = (ImageReaderSpi) clazz.newInstance();
                else
                    suggestedSPI = null;
            } catch (Exception e) {
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
                suggestedSPI = null;
            }
        }

        // caching for the index
        cachingIndex = configuration.isCaching();

        // imposed BBOX
        if (configuration.getEnvelope() != null) {
            this.imposedBBox = true;
            // we set the BBOX later to retain also the CRS
        } else {
            this.imposedBBox = false;
        }

        // typeName to be used for reading the mosaic
        this.typeName = configuration.getTypeName();

    }

    /**
     * Constructor.
     * 
     * @param source
     *            The source object.
     * @throws IOException
     * @throws UnsupportedEncodingException
     * 
     */
    public ImageMosaicReader(Object source) throws IOException {
        this(source, null);

    }

    /**
     * 
     * @see org.opengis.coverage.grid.GridCoverageReader#getFormat()
     */
    public Format getFormat() {
        return new ImageMosaicFormat();
    }

    /**
     * 
     * @see org.opengis.coverage.grid.GridCoverageReader#read(org.opengis.parameter.GeneralParameterValue[])
     */
    public GridCoverage2D read(GeneralParameterValue[] params) throws IOException {

        // check if we were disposed already
        if (rasterManager == null) {
            throw new IOException(
                    "Looks like this reader has been already disposed or it has not been properly initialized.");
        }
        if (LOGGER.isLoggable(Level.FINE)) {
            if (sourceURL != null) {
                LOGGER.fine("Reading mosaic from " + sourceURL.toString());
            } else {
                LOGGER.fine("Reading mosaic");
            }
            LOGGER.fine("Highest res " + highestRes[0] + " " + highestRes[1]);
        }
        //
        // add max allowed tiles if missing
        //
        if (this.maxAllowedTiles != ImageMosaicFormat.MAX_ALLOWED_TILES.getDefaultValue()) {
            if (params != null) {
                // first thing let's see if we have it already, in which case we do nothing since a read parameter override a Hint
                boolean found = false;
                for (GeneralParameterValue pv : params) {
                    if (pv.getDescriptor().getName().equals(ImageMosaicFormat.MAX_ALLOWED_TILES.getName())) {
                        found = true;
                        break;
                    }
                }

                //ok, we did not find it, let's add it back 
                if (!found) {
                    final GeneralParameterValue[] temp = new GeneralParameterValue[params.length + 1];
                    System.arraycopy(params, 0, temp, 0, params.length);
                    ParameterValue<Integer> tempVal = ImageMosaicFormat.MAX_ALLOWED_TILES.createValue();
                    tempVal.setValue(this.maxAllowedTiles);
                    temp[params.length] = tempVal;
                }
            } else {
                // we do not have nay read params, we have to create the array for them
                ParameterValue<Integer> tempVal = ImageMosaicFormat.MAX_ALLOWED_TILES.createValue();
                tempVal.setValue(this.maxAllowedTiles);
                params = new GeneralParameterValue[] { tempVal };
            }

        }

        //
        // Loading tiles trying to optimize as much as possible
        //
        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();
        }
    }

    /**
     * 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 getNumberOfOvervies() {
        return super.numOverviews;
    }

    /** Package scope grid to world transformation accessor */
    MathTransform getRaster2Model() {
        return raster2Model;
    }

    /**
     * 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;
    }

    String getName() {
        return super.coverageName;
    }

    /**
     * Number of coverages for this reader is 1
     * 
     * @return the number of coverages for this reader.
     */
    @Override
    public int getGridCoverageCount() {
        return 1;
    }

    /**
     * Releases resources held by this reader.
     * 
     */
    @Override
    public synchronized void dispose() {
        super.dispose();
        try {
            if (rasterManager != null)
                rasterManager.dispose();
        } catch (Exception e) {
            if (LOGGER.isLoggable(Level.FINE))
                LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
        } finally {
            rasterManager = null;
        }
    }

    @Override
    public String[] getMetadataNames() {
        final String[] parentNames = super.getMetadataNames();
        final List<String> metadataNames = new ArrayList<String>();
        metadataNames.add(TIME_DOMAIN);
        metadataNames.add(HAS_TIME_DOMAIN);
        metadataNames.add(TIME_DOMAIN_MINIMUM);
        metadataNames.add(TIME_DOMAIN_MAXIMUM);
        metadataNames.add(TIME_DOMAIN_RESOLUTION);
        metadataNames.add(ELEVATION_DOMAIN);
        metadataNames.add(ELEVATION_DOMAIN_MINIMUM);
        metadataNames.add(ELEVATION_DOMAIN_MAXIMUM);
        metadataNames.add(HAS_ELEVATION_DOMAIN);
        metadataNames.add(ELEVATION_DOMAIN_RESOLUTION);
        if (domainsManager != null) {
            metadataNames.addAll(domainsManager.getMetadataNames());
        }
        if (parentNames != null)
            metadataNames.addAll(Arrays.asList(parentNames));
        return metadataNames.toArray(new String[metadataNames.size()]);
    }

    @Override
    public String getMetadataValue(final String name) {
        String value = super.getMetadataValue(name);
        if (value != null) {
            return value;
        }
        final boolean hasTimeDomain = timeDomainManager != null;
        final boolean hasElevationDomain = elevationDomainManager != null;

        if (name.equalsIgnoreCase(HAS_ELEVATION_DOMAIN))
            return String.valueOf(hasElevationDomain);

        if (name.equalsIgnoreCase(HAS_TIME_DOMAIN)) {
            return String.valueOf(hasTimeDomain);
        }

        // NOT supported
        if (name.equalsIgnoreCase(TIME_DOMAIN_RESOLUTION)) {
            return null;
        }
        // NOT supported
        if (name.equalsIgnoreCase(ELEVATION_DOMAIN_RESOLUTION)) {
            return null;
        }

        if (hasTimeDomain) {
            if (name.equalsIgnoreCase("time_domain")) {
                return timeDomainManager.getMetadataValue(name);
            }
            if ((name.equalsIgnoreCase("time_domain_minimum") || name.equalsIgnoreCase("time_domain_maximum"))) {
                return timeDomainManager.getMetadataValue(name);
            }
        }

        if (hasElevationDomain) {
            if (name.equalsIgnoreCase("elevation_domain")) {
                return elevationDomainManager.getMetadataValue(name);
            }

            if (name.equalsIgnoreCase("elevation_domain_minimum")
                    || name.equalsIgnoreCase("elevation_domain_maximum")) {
                return elevationDomainManager.getMetadataValue(name);
            }

        }

        // check additional domains
        if (domainsManager != null) {
            return domainsManager.getMetadataValue(name);
        }

        // 
        return value;
    }

    @Override
    public Set<ParameterDescriptor<List>> getDynamicParameters() {
        return domainsManager != null ? domainsManager.getDynamicParameters() : super.getDynamicParameters();
    }

    public boolean isParameterSupported(Identifier name) {
        return domainsManager != null ? domainsManager.isParameterSupported(name) : false;
    }
}