Package org.geotools.gce.imagemosaic

Source Code of org.geotools.gce.imagemosaic.GranuleDescriptor$GranuleLoadingResult

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2007-2013, 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 it.geosolutions.imageio.pam.PAMDataset;
import it.geosolutions.imageio.pam.PAMParser;
import it.geosolutions.imageio.utilities.ImageIOUtilities;

import java.awt.Dimension;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.imageio.spi.ImageInputStreamSpi;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.stream.ImageInputStream;
import javax.media.jai.BorderExtender;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.ROIShape;
import javax.media.jai.TileCache;
import javax.media.jai.TileScheduler;

import org.apache.commons.beanutils.MethodUtils;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.data.DataUtilities;
import org.geotools.factory.Hints;
import org.geotools.gce.imagemosaic.catalog.MultiLevelROI;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.image.io.ImageIOExt;
import org.geotools.image.jai.Registry;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.resources.coverage.CoverageUtilities;
import org.geotools.resources.geometry.XRectangle2D;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.image.ImageUtilities;
import org.jaitools.imageutils.ROIGeometry;
import org.jaitools.media.jai.vectorbinarize.VectorBinarizeDescriptor;
import org.jaitools.media.jai.vectorbinarize.VectorBinarizeRIF;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Geometry;

/**
* A granuleDescriptor is a single piece of the mosaic, with its own overviews and
* everything.
*
* <p>
* This class is responsible for caching the various size of the different
* levels of each single granuleDescriptor since computing them each time is expensive
* (opening a file, looking for a reader, parsing metadata,etc...).
*
* <p>
* Right now we are making the assumption that a single granuleDescriptor is made a by a
* single file with embedded overviews, either explicit or intrinsic through wavelets like MrSID,
* ECW or JPEG2000.
*     
* @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.5.5
*
*
* @source $URL$
*/
public class GranuleDescriptor {
   
  /** Logger. */
  private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GranuleDescriptor.class);

    private static final String AUXFILE_EXT = ".aux.xml";

    static {
        try {
            Registry.registerRIF(JAI.getDefaultInstance(), new VectorBinarizeDescriptor(),
                    new VectorBinarizeRIF(), Registry.JAI_TOOLS_PRODUCT);
        } catch (Exception e) {
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.log(Level.FINE, e.getLocalizedMessage());
            }
        }
    }
 
 
    OverviewsController overviewsController;
   
  /**
   * This class represent an overview level in a single granuleDescriptor.
   *
   * <p> Notice that the internal transformations for the various levels are reffered to the corner, rather than to the centre.
   * @author Simone Giannecchini, GeoSolutions S.A.S.
   *
   */
  class GranuleOverviewLevelDescriptor{

    final double scaleX;
   
    final double scaleY;
   
    final int width;
   
    final int height;

    final AffineTransform2D baseToLevelTransform;
   
    final AffineTransform2D gridToWorldTransformCorner;

    final Rectangle rasterDimensions;
   
   
    public AffineTransform getBaseToLevelTransform() {
      return baseToLevelTransform;
    }

    public double getScaleX() {
      return scaleX;
    }

    public double getScaleY() {
      return scaleY;
    }

    public int getWidth() {
      return width;
    }

    public int getHeight() {
      return height;
    }

    public GranuleOverviewLevelDescriptor(final double scaleX,final double scaleY,final int width,final int height) {
      this.scaleX = scaleX;
      this.scaleY = scaleY;
      this.baseToLevelTransform=new AffineTransform2D( XAffineTransform.getScaleInstance(scaleX,scaleY,0,0));
     
      final AffineTransform gridToWorldTransform_ = new AffineTransform(baseToLevelTransform);
      gridToWorldTransform_.preConcatenate(CoverageUtilities.CENTER_TO_CORNER);
      gridToWorldTransform_.preConcatenate(baseGridToWorld);
      this.gridToWorldTransformCorner=new AffineTransform2D(gridToWorldTransform_);
      this.width = width;
      this.height = height;
      this.rasterDimensions= new Rectangle(0,0,width,height);
    }

    public Rectangle getBounds() {
      return (Rectangle) rasterDimensions.clone();
    }

    public AffineTransform2D getGridToWorldTransform() {
      return gridToWorldTransformCorner;
    }   
   


    @Override
    public String toString() {
      // build a decent representation for this level
      final StringBuilder buffer = new StringBuilder();
      buffer.append("Description of a granuleDescriptor level").append("\n")
      .append("width:\t\t").append(width).append("\n")
      .append("height:\t\t").append(height).append("\n")
      .append("scaleX:\t\t").append(scaleX).append("\n")
      .append("scaleY:\t\t").append(scaleY).append("\n")
      .append("baseToLevelTransform:\t\t").append(baseToLevelTransform.toString()).append("\n")
      .append("gridToWorldTransform:\t\t").append(gridToWorldTransformCorner.toString()).append("\n");
      return buffer.toString();
    }
   
  }
 
  /**
     * Simple placeholder class to store the result of a Granule Loading
     * which comprises of a raster as well as a {@link ROIShape} for its footprint.
     *
     * @author Daniele Romagnoli, GeoSolutions S.A.S.
     *
     */
    static class GranuleLoadingResult {

        RenderedImage loadedImage;

        ROI footprint;

        URL granuleUrl;

        boolean doFiltering;

        PAMDataset pamDataset;

        public ROI getFootprint() {
            return footprint;
        }

        public RenderedImage getRaster() {
            return loadedImage;
        }

        public URL getGranuleUrl() {
            return granuleUrl;
        }
        public PAMDataset getPamDataset() {
            return pamDataset;
        }

        public void setPamDataset(PAMDataset pamDataset) {
            this.pamDataset = pamDataset;
        }

        public boolean isDoFiltering() {
            return doFiltering;
        }

        GranuleLoadingResult(RenderedImage loadedImage, ROI footprint, URL granuleUrl, final boolean doFiltering, final PAMDataset pamDataset) {
            this.loadedImage = loadedImage;
            Object roi = loadedImage.getProperty("ROI");
            if(roi instanceof ROI) {
                this.footprint = (ROI) roi;
            }           
            this.granuleUrl = granuleUrl;
            this.doFiltering = doFiltering;
            this.pamDataset = pamDataset;
        }
    }

    private static PAMParser pamParser = PAMParser.getInstance();
   
    ReferencedEnvelope granuleBBOX;
 
  MultiLevelROI roiProvider;
       
  URL granuleUrl;
 
  int maxDecimationFactor = -1;
 
  final Map<Integer,GranuleOverviewLevelDescriptor> granuleLevels= Collections.synchronizedMap(new HashMap<Integer,GranuleOverviewLevelDescriptor>());
 
  AffineTransform baseGridToWorld;
 
  ImageReaderSpi cachedReaderSPI;

  SimpleFeature originator;
 
  PAMDataset pamDataset;
 
  boolean handleArtifactsFiltering = false;
 
  boolean filterMe = false;

        ImageInputStreamSpi cachedStreamSPI;

        private GridToEnvelopeMapper geMapper;

        private boolean singleDimensionalGranule;
       
  private void init(final BoundingBox granuleBBOX, final URL granuleUrl,
      final ImageReaderSpi suggestedSPI, final MultiLevelROI roiProvider,
      final boolean heterogeneousGranules, final boolean handleArtifactsFiltering, final Hints hints) {
    this.granuleBBOX = ReferencedEnvelope.reference(granuleBBOX);
    this.granuleUrl = granuleUrl;
    this.roiProvider = roiProvider;
    this.singleDimensionalGranule = true;
    this.handleArtifactsFiltering = handleArtifactsFiltering;
        filterMe = handleArtifactsFiltering && roiProvider != null;
               
   
    // create the base grid to world transformation
    ImageInputStream inStream = null;
    ImageReader reader = null;
    try {
      //
      //get info about the raster we have to read
      //
     
      // get a stream
            if(cachedStreamSPI==null){
                cachedStreamSPI = Utils.getInputStreamSPIFromURL(granuleUrl);
            }
            assert cachedStreamSPI!=null:"no cachedStreamSPI available!";
      inStream = cachedStreamSPI.createInputStreamInstance(granuleUrl, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
      if(inStream == null){
                            final File file = DataUtilities.urlToFile(granuleUrl);
                            if(file!=null){
                                if(LOGGER.isLoggable(Level.WARNING)){
                                    LOGGER.log(Level.WARNING,Utils.getFileInfo(file));
                                }
                            }
          throw new IllegalArgumentException("Unable to get an input stream for the provided file "+granuleUrl.toString());
      }
     
      // get a reader and try to cache the suggested SPI first
      if(cachedReaderSPI == null){
          cachedReaderSPI = Utils.getReaderSpiFromStream(suggestedSPI, inStream);
       
      }
      if (reader == null) {
          if (cachedReaderSPI == null) {
              throw new IllegalArgumentException("Unable to get a ReaderSPI for the provided input: " + granuleUrl.toString());
          }
          reader = cachedReaderSPI.createReaderInstance();
      }
     
      if(reader == null)
        throw new IllegalArgumentException("Unable to get an ImageReader for the provided file "+granuleUrl.toString());
      boolean ignoreMetadata = customizeReaderInitialization(reader, hints);
      reader.setInput(inStream, false, ignoreMetadata);
      //get selected level and base level dimensions
      final Rectangle originalDimension = Utils.getDimension(0, reader);
     
      // build the g2W for this tile, in principle we should get it
      // somehow from the tile itself or from the index, but at the moment
      // we do not have such info, hence we assume that it is a simple
      // scale and translate
      this.geMapper= new GridToEnvelopeMapper(new GridEnvelope2D(originalDimension), granuleBBOX);
      geMapper.setPixelAnchor(PixelInCell.CELL_CENTER);//this is the default behavior but it is nice to write it down anyway
      this.baseGridToWorld = geMapper.createAffineTransform();
     
      // add the base level
      this.granuleLevels.put(Integer.valueOf(0), new GranuleOverviewLevelDescriptor(1, 1, originalDimension.width, originalDimension.height));
     
      ////////////////////// Setting overviewController ///////////////////////
      if (heterogeneousGranules) {
          // //
          //
          // Right now we are setting up overviewsController by assuming that
          // overviews are internal images as happens in TIFF images
          // We can improve this by leveraging on coverageReaders 
          //
          // //
         
                      // Getting the first level descriptor
                            final GranuleOverviewLevelDescriptor baseOverviewLevelDescriptor = granuleLevels.get(0);

                            // Variables initialization
          final int numberOfOvervies = reader.getNumImages(true) - 1;
          final AffineTransform2D baseG2W = baseOverviewLevelDescriptor.getGridToWorldTransform();
          final int width = baseOverviewLevelDescriptor.getWidth();
          final int height = baseOverviewLevelDescriptor.getHeight();
          final double resX = AffineTransform2D.getScaleX0(baseG2W);
          final double resY = AffineTransform2D.getScaleY0(baseG2W);
          final double[] highestRes = new double[]{resX, resY};
          final double[][] overviewsResolution = new double[numberOfOvervies][2];
         
          // Populating overviews and initializing overviewsController
          for (int i = 0; i < numberOfOvervies; i++){
              overviewsResolution[i][0]= (highestRes[0] * width) / reader.getWidth(i + 1);
              overviewsResolution[i][1]= (highestRes[1] * height) / reader.getHeight(i + 1);
          }
          overviewsController = new OverviewsController(highestRes, numberOfOvervies, overviewsResolution);
      }
                        //////////////////////////////////////////////////////////////////////////
     
      if (hints != null && hints.containsKey(Utils.CHECK_AUXILIARY_METADATA)) {
          boolean checkAuxiliaryMetadata = (Boolean) hints.get(Utils.CHECK_AUXILIARY_METADATA);
          if (checkAuxiliaryMetadata) {
              checkPamDataset();
          }
      }
     

    } catch (IllegalStateException e) {
      throw new IllegalArgumentException(e);
     
    } catch (IOException e) {
      throw new IllegalArgumentException(e);
    } finally {
        // close/dispose stream and readers
      try {
        if(inStream != null){
          inStream.close();
        }
      } catch (Throwable e) {
        throw new IllegalArgumentException(e);
      } finally{
        if (reader != null){
          reader.dispose();
        }
      }
    }
  }
 
  /**
   * Look for GDAL Auxiliary File and unmarshall it to setup a PamDataset if available
   * @throws IOException
   */
  private void checkPamDataset() throws IOException {
      final File file = DataUtilities.urlToFile(granuleUrl);
            final String path = file.getCanonicalPath();
            final String auxFile = path + AUXFILE_EXT;
            pamDataset = pamParser.parsePAM(auxFile);
    }

    private boolean customizeReaderInitialization(ImageReader reader, Hints hints) {
            String classString = reader.getClass().getSuperclass().getName();
            // Special Management for NetCDF readers to set external Auxiliary File
            if (hints != null && hints.containsKey(Utils.AUXILIARY_FILES_PATH)) {
                if (classString.equalsIgnoreCase("org.geotools.imageio.GeoSpatialImageReader")) {
                    try {
                        String auxiliaryFilePath = (String) hints.get(Utils.AUXILIARY_FILES_PATH);
                        if (hints.containsKey(Utils.PARENT_DIR)) {
                            String parentDir = (String) hints.get(Utils.PARENT_DIR);
                            // if the path stars with the parentDir, it's already absolute (old configuration file)
                            if (!auxiliaryFilePath.startsWith(parentDir)) {
                                auxiliaryFilePath = parentDir + File.separatorChar + auxiliaryFilePath;
                            }
                        }
                        MethodUtils.invokeMethod(reader, "setAuxiliaryFilesPath", auxiliaryFilePath);
                        singleDimensionalGranule = false;
                        return true;
                    } catch (NoSuchMethodException e) {
                        throw new RuntimeException(e);
                    } catch (IllegalAccessException e) {
                        throw new RuntimeException(e);
                    } catch (InvocationTargetException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
            return false;
       
    }

    public GranuleDescriptor(
                final String granuleLocation,
                final BoundingBox granuleBBox,
                final ImageReaderSpi suggestedSPI,
                final MultiLevelROI roiProvider) {
      this (granuleLocation, granuleBBox, suggestedSPI, roiProvider, -1, false);
  }
 
  public GranuleDescriptor(
                final String granuleLocation,
                final BoundingBox granuleBBox,
                final ImageReaderSpi suggestedSPI,
                final MultiLevelROI roiProvider,
                final boolean heterogeneousGranules) {
            this (granuleLocation, granuleBBox, suggestedSPI, roiProvider, -1, heterogeneousGranules);
        }
 
  public GranuleDescriptor(
                final String granuleLocation,
                final BoundingBox granuleBBox,
                final ImageReaderSpi suggestedSPI,
                final MultiLevelROI roiProvider,
                final int maxDecimationFactor){
      this(granuleLocation, granuleBBox, suggestedSPI, roiProvider, maxDecimationFactor, false);
     
  }
 
  public GranuleDescriptor(
          final String granuleLocation,
                final BoundingBox granuleBBox,
                final ImageReaderSpi suggestedSPI,
                final MultiLevelROI roiProvider,
                final int maxDecimationFactor,
                final boolean heterogeneousGranules) {
    this(granuleLocation, granuleBBox, suggestedSPI, roiProvider, maxDecimationFactor, heterogeneousGranules, false);       
    }

  public GranuleDescriptor(
          final String granuleLocation,
                final BoundingBox granuleBBox,
                final ImageReaderSpi suggestedSPI,
                final MultiLevelROI roiProvider,
                final int maxDecimationFactor,
                final boolean heterogeneousGranules,
        final boolean handleArtifactsFiltering) {

      this.maxDecimationFactor = maxDecimationFactor;
            final URL rasterFile = DataUtilities.fileToURL(new File(granuleLocation));
           
            if (rasterFile == null) {
                    return;
            }
           
            if (LOGGER.isLoggable(Level.FINER)) {
                LOGGER.finer("File found " + granuleLocation);
            }
   
            this.originator = null;
            init (granuleBBox, rasterFile, suggestedSPI, roiProvider, heterogeneousGranules, handleArtifactsFiltering, null);
       
  }
 
    /**
     *
     * @param feature
     * @param suggestedSPI
     * @param pathType
     * @param locationAttribute
     * @param parentLocation
     */
    public GranuleDescriptor(
            final SimpleFeature feature,
            final ImageReaderSpi suggestedSPI,
            final PathType pathType,
            final String locationAttribute,
            final String parentLocation) {
                this(feature,suggestedSPI,pathType,locationAttribute,parentLocation, false);
                }
   
    public GranuleDescriptor(SimpleFeature feature, ImageReaderSpi suggestedSPI,
            PathType pathType, String locationAttribute, String parentLocation,
            boolean heterogeneousGranules, Hints hints) {
        this(feature,suggestedSPI,pathType,locationAttribute,parentLocation, null, heterogeneousGranules, hints);
       
    }
   
    public GranuleDescriptor(SimpleFeature feature, ImageReaderSpi suggestedSPI,
            PathType pathType, String locationAttribute, String parentLocation,
            boolean heterogeneousGranules) {
        this(feature,suggestedSPI,pathType,locationAttribute,parentLocation, heterogeneousGranules, null);
    }
   
    /**
     * Constructor for the {@link GranuleDescriptor} assuming it doesn't belong to an
     *  heterogeneous granules set.
     * @param feature a {@link SimpleFeature} referring to that granule
     * @param suggestedSPI the suggested {@link ImageReaderSpi} to be used to get a reader
     *          to handle this granule.
     * @param pathType A {@link PathType} identifying if the granule location should be resolved as
     *          a relative or an absolute path.
     * @param locationAttribute the attribute containing the granule location.
     * @param parentLocation the location of the parent of that granule.
     * @param inclusionGeometry the footprint of that granule (if any). It may be null.
     */
    public GranuleDescriptor(
            SimpleFeature feature,
            ImageReaderSpi suggestedSPI,
            PathType pathType,
            final String locationAttribute,
            final String parentLocation,
            final MultiLevelROI roiProvider) {
        this(feature,suggestedSPI,pathType,locationAttribute,parentLocation, roiProvider, false, null);
    }
   
    /**
     * Constructor for the {@link GranuleDescriptor}
     * @param feature a {@link SimpleFeature} referring to that granule
     * @param suggestedSPI the suggested {@link ImageReaderSpi} to be used to get a reader
     *          to handle this granule.
     * @param pathType A {@link PathType} identifying if the granule location should be resolved as
     *          a relative or an absolute path.
     * @param locationAttribute the attribute containing the granule location.
     * @param parentLocation the location of the parent of that granule.
     * @param inclusionGeometry the footprint of that granule (if any). It may be null.
     * @param heterogeneousGranules if {@code true}, this granule belongs to a set of heterogeneous granules
     */
  public GranuleDescriptor(
      final SimpleFeature feature,
      final ImageReaderSpi suggestedSPI,
      final PathType pathType,
      final String locationAttribute,
      final String parentLocation,
      final MultiLevelROI roiProvider,
      final boolean heterogeneousGranules,
      final Hints hints) {
    // Get location and envelope of the image to load.
    final String granuleLocation = (String) feature.getAttribute(locationAttribute);
    final ReferencedEnvelope granuleBBox = ReferencedEnvelope.reference(feature.getBounds());
   

    // If the granuleDescriptor is not there, dump a message and continue
    final URL rasterFile = pathType.resolvePath(parentLocation, granuleLocation);
    if (rasterFile == null) {
                  throw new IllegalArgumentException(Errors.format(ErrorKeys.ILLEGAL_ARGUMENT_$2,"granuleLocation",granuleLocation));       
     
    }
    if (LOGGER.isLoggable(Level.FINE))
      LOGGER.fine("File found "+granuleLocation);

    this.originator=feature;
    init(granuleBBox,rasterFile,suggestedSPI, roiProvider, heterogeneousGranules, false, hints);
   
   
  }

    /**
   * Load a specified a raster as a portion of the granule describe by this {@link GranuleDescriptor}.
   *
   * @param imageReadParameters the {@link ImageReadParam} to use for reading.
   * @param index the index to use for the {@link ImageReader}.
   * @param cropBBox the bbox to use for cropping.
   * @param mosaicWorldToGrid the cropping grid to world transform.
   * @param request the incoming request to satisfy.
   * @param hints {@link Hints} to be used for creating this raster.
   * @return a specified a raster as a portion of the granule describe by this {@link GranuleDescriptor}.
   * @throws IOException in case an error occurs.
   */
  public GranuleLoadingResult loadRaster(
      final ImageReadParam imageReadParameters,
      final int index,
      final ReferencedEnvelope cropBBox,
      final MathTransform2D mosaicWorldToGrid,
      final RasterLayerRequest request,
      final Hints hints) throws IOException {
   
    if (LOGGER.isLoggable(java.util.logging.Level.FINER)){
        final String name = Thread.currentThread().getName();
      LOGGER.finer("Thread:" + name + " Loading raster data for granuleDescriptor "+this.toString());
    }
    ImageReadParam readParameters = null;
    int imageIndex;
    final boolean useFootprint = roiProvider != null&&request.getFootprintBehavior()!=FootprintBehavior.None;
    Geometry inclusionGeometry = useFootprint ? roiProvider.getFootprint(): null;
    final ReferencedEnvelope bbox = useFootprint?
            new ReferencedEnvelope(granuleBBOX.intersection(inclusionGeometry.getEnvelopeInternal()), granuleBBOX.getCoordinateReferenceSystem()):
                granuleBBOX;
    boolean doFiltering = false;
                if (filterMe && useFootprint){
                    doFiltering = Utils.areaIsDifferent(inclusionGeometry, baseGridToWorld, granuleBBOX);
                }
   
   
                // intersection of this tile bound with the current crop bbox
                final ReferencedEnvelope intersection = new ReferencedEnvelope(bbox.intersection(cropBBox), cropBBox.getCoordinateReferenceSystem());
                if (intersection.isEmpty()) {
                    if (LOGGER.isLoggable(java.util.logging.Level.FINE)){
                            LOGGER.fine(new StringBuilder("Got empty intersection for granule ").append(this.toString())
                                    .append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString());
                    }
                    return null;
                }
               
        // check if the requested bbox intersects or overlaps the requested area
        if(useFootprint && inclusionGeometry != null && !JTS.toGeometry(cropBBox).intersects(inclusionGeometry)) {
            if (LOGGER.isLoggable(java.util.logging.Level.FINE)) {
                LOGGER.fine(new StringBuilder("Got empty intersection for granule ").append(this.toString())
                        .append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString());
            }
            return null;
        }
               

    ImageInputStream inStream=null;
    ImageReader reader=null;
    try {
      //
      //get info about the raster we have to read
      //
     
      // get a stream
            assert cachedStreamSPI!=null:"no cachedStreamSPI available!";
                        inStream = cachedStreamSPI.createInputStreamInstance(granuleUrl, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
      if(inStream==null)
        return null;
     
 
      // get a reader and try to cache the relevant SPI
      if(cachedReaderSPI==null){
        reader = ImageIOExt.getImageioReader(inStream);
        if(reader!=null)
          cachedReaderSPI=reader.getOriginatingProvider();
      }
      else
        reader=cachedReaderSPI.createReaderInstance();
      if(reader==null) {
        if (LOGGER.isLoggable(java.util.logging.Level.WARNING)){
          LOGGER.warning(new StringBuilder("Unable to get s reader for granuleDescriptor ").append(this.toString())
                  .append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString());
        }
        return null;
      }
      // set input
      customizeReaderInitialization(reader, hints);
      reader.setInput(inStream);
     
            // Checking for heterogeneous granules and if the mosaic is not multidimensional
            if (request.isHeterogeneousGranules() && singleDimensionalGranule) {
          // create read parameters
          readParameters = new ImageReadParam();
         
          //override the overviews controller for the base layer
          imageIndex = ReadParamsController.setReadParams(
                  request.spatialRequestHelper.getComputedResolution(),
                  request.getOverviewPolicy(),
                  request.getDecimationPolicy(),
                  readParameters,
                  request.rasterManager,
                  overviewsController);
      } else {
          imageIndex = index;
          readParameters = imageReadParameters;
      }
     
      //get selected level and base level dimensions
      final GranuleOverviewLevelDescriptor selectedlevel= getLevel(imageIndex,reader);
 
     
      // now create the crop grid to world which can be used to decide
      // which source area we need to crop in the selected level taking
      // into account the scale factors imposed by the selection of this
      // level together with the base level grid to world transformation
            AffineTransform2D cropWorldToGrid= new AffineTransform2D(selectedlevel.gridToWorldTransformCorner);
            cropWorldToGrid=(AffineTransform2D) cropWorldToGrid.inverse();
      // computing the crop source area which lives into the
      // selected level raster space, NOTICE that at the end we need to
      // take into account the fact that we might also decimate therefore
      // we cannot just use the crop grid to world but we need to correct
      // it.
      final Rectangle sourceArea = CRS.transform(cropWorldToGrid, intersection).toRectangle2D().getBounds();
      //gutter
      if(selectedlevel.baseToLevelTransform.isIdentity()){
          sourceArea.grow(2, 2);
      }
      XRectangle2D.intersect(sourceArea, selectedlevel.rasterDimensions, sourceArea);//make sure roundings don't bother us
      // is it empty??
      if (sourceArea.isEmpty()) {
        if (LOGGER.isLoggable(java.util.logging.Level.FINE)){
          LOGGER.fine("Got empty area for granuleDescriptor "+this.toString()+
                  " with request "+request.toString()+" Resulting in no granule loaded: Empty result");
         
        }
        return null;

      } else if (LOGGER.isLoggable(java.util.logging.Level.FINER)){
        LOGGER.finer( "Loading level " + imageIndex + " with source region: "
                + sourceArea + " subsampling: "
                + readParameters.getSourceXSubsampling() + ","
                + readParameters.getSourceYSubsampling() + " for granule:"
                + granuleUrl) ;
      }

      // Setting subsampling
      int newSubSamplingFactor = 0;
      final String pluginName = cachedReaderSPI.getPluginClassName();
      if (pluginName != null && pluginName.equals(ImageUtilities.DIRECT_KAKADU_PLUGIN)){
        final int ssx = readParameters.getSourceXSubsampling();
        final int ssy = readParameters.getSourceYSubsampling();
        newSubSamplingFactor = ImageIOUtilities.getSubSamplingFactor2(ssx, ssy);
        if (newSubSamplingFactor != 0) {
            if (newSubSamplingFactor > maxDecimationFactor && maxDecimationFactor != -1){
                newSubSamplingFactor = maxDecimationFactor;
            }
            readParameters.setSourceSubsampling(newSubSamplingFactor, newSubSamplingFactor,0,0);
        }
      }
     
      // set the source region
      readParameters.setSourceRegion(sourceArea);
      RenderedImage raster;
      try {
        // read
        raster= request.getReadType().read(readParameters,imageIndex, granuleUrl, selectedlevel.rasterDimensions, reader, hints,false);
       
      } catch (Throwable e) {
        if (LOGGER.isLoggable(java.util.logging.Level.FINE)){
          LOGGER.log(java.util.logging.Level.FINE,
                  "Unable to load raster for granuleDescriptor "
                  + this.toString() + " with request "
                  + request.toString() + " Resulting in no granule loaded: Empty result",e);
        }
        return null;
      }

      // use fixed source area
      sourceArea.setRect(readParameters.getSourceRegion());
     
      //
      // setting new coefficients to define a new affineTransformation
      // to be applied to the grid to world transformation
      // -----------------------------------------------------------------------------------
      //
      // With respect to the original envelope, the obtained planarImage
      // needs to be rescaled. The scaling factors are computed as the
      // ratio between the cropped source region sizes and the read
      // image sizes.
      //
      // place it in the mosaic using the coords created above;
      double decimationScaleX =  ((1.0 * sourceArea.width) / raster.getWidth());
      double decimationScaleY =  ((1.0 * sourceArea.height) / raster.getHeight());
      final AffineTransform decimationScaleTranform = XAffineTransform.getScaleInstance(decimationScaleX, decimationScaleY);

      // keep into account translation  to work into the selected level raster space
      final AffineTransform afterDecimationTranslateTranform =XAffineTransform.getTranslateInstance(sourceArea.x, sourceArea.y);
     
      // now we need to go back to the base level raster space
      final AffineTransform backToBaseLevelScaleTransform =selectedlevel.baseToLevelTransform;
     
      // now create the overall transform
      final AffineTransform finalRaster2Model = new AffineTransform(baseGridToWorld);
      finalRaster2Model.concatenate(CoverageUtilities.CENTER_TO_CORNER);
                       
      if(!XAffineTransform.isIdentity(backToBaseLevelScaleTransform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(backToBaseLevelScaleTransform);
      if(!XAffineTransform.isIdentity(afterDecimationTranslateTranform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(afterDecimationTranslateTranform);
      if(!XAffineTransform.isIdentity(decimationScaleTranform, Utils.AFFINE_IDENTITY_EPS))
        finalRaster2Model.concatenate(decimationScaleTranform);
     
            // adjust roi
            if (useFootprint) {

                ROIGeometry transformed;
                try {
                    transformed = roiProvider.getTransformedROI(finalRaster2Model.createInverse());
                    if (transformed.getAsGeometry().isEmpty()) {
                        // inset might have killed the geometry fully
                        return null;
                    }

                    PlanarImage pi = PlanarImage.wrapRenderedImage(raster);
                    if(!transformed.intersects(pi.getBounds())) {
                        return null;
                    }
                    pi.setProperty("ROI", transformed);
                    raster = pi;

                } catch (NoninvertibleTransformException e) {
                    if (LOGGER.isLoggable(java.util.logging.Level.INFO))
                        LOGGER.info("Unable to create a granuleDescriptor " + this.toString()
                                + " due to a problem when managing the ROI");
                    return null;
                }

            }
      // keep into account translation factors to place this tile
      finalRaster2Model.preConcatenate((AffineTransform) mosaicWorldToGrid);
      final Interpolation interpolation = request.getInterpolation();
     
     
      //paranoiac check to avoid that JAI freaks out when computing its internal layouT on images that are too small
      Rectangle2D finalLayout= ImageUtilities.layoutHelper(
          raster,
          (float)finalRaster2Model.getScaleX(),
          (float)finalRaster2Model.getScaleY(),
          (float)finalRaster2Model.getTranslateX(),
          (float)finalRaster2Model.getTranslateY(),
          interpolation);
      if(finalLayout.isEmpty()){
        if(LOGGER.isLoggable(java.util.logging.Level.INFO))
          LOGGER.info("Unable to create a granuleDescriptor " + this.toString()
                  + " due to jai scale bug creating a null source area");
        return null;
      }
     
                       
      // apply the affine transform  conserving indexed color model
      final RenderingHints localHints = new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, interpolation instanceof InterpolationNearest? Boolean.FALSE:Boolean.TRUE);
      if(XAffineTransform.isIdentity(finalRaster2Model,Utils.AFFINE_IDENTITY_EPS)) {
          return new GranuleLoadingResult(raster, null, granuleUrl, doFiltering, pamDataset);
      } else {
        //
        // In case we are asked to use certain tile dimensions we tile
        // also at this stage in case the read type is Direct since
        // buffered images comes up untiled and this can affect the
        // performances of the subsequent affine operation.
        //
        final Dimension tileDimensions=request.getTileDimensions();
        if(tileDimensions!=null&&request.getReadType().equals(ReadType.DIRECT_READ)) {
          final ImageLayout layout = new ImageLayout();
          layout.setTileHeight(tileDimensions.width).setTileWidth(tileDimensions.height);
          localHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout));
        } else {
            if (hints != null && hints.containsKey(JAI.KEY_IMAGE_LAYOUT)) {
                          final Object layout = hints.get(JAI.KEY_IMAGE_LAYOUT);
                          if (layout != null && layout instanceof ImageLayout) {
                              localHints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT, ((ImageLayout) layout).clone()));
                          }
                      }
        }
        if (hints != null && hints.containsKey(JAI.KEY_TILE_CACHE)){
            final Object cache = hints.get(JAI.KEY_TILE_CACHE);
            if (cache != null && cache instanceof TileCache)
                localHints.add(new RenderingHints(JAI.KEY_TILE_CACHE, (TileCache) cache));
        }
        if (hints != null && hints.containsKey(JAI.KEY_TILE_SCHEDULER)){
                                    final Object scheduler = hints.get(JAI.KEY_TILE_SCHEDULER);
                                    if (scheduler != null && scheduler instanceof TileScheduler)
                                        localHints.add(new RenderingHints(JAI.KEY_TILE_SCHEDULER, (TileScheduler) scheduler));
                                }
        boolean addBorderExtender = true;
                if (hints != null && hints.containsKey(JAI.KEY_BORDER_EXTENDER)) {
                    final Object extender = hints.get(JAI.KEY_BORDER_EXTENDER);
                    if (extender != null && extender instanceof BorderExtender) {
                        localHints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER, (BorderExtender) extender));
                        addBorderExtender = false;
                    }
                }
                // BORDER extender
                if (addBorderExtender) {
                    localHints.add(ImageUtilities.BORDER_EXTENDER_HINTS);
                }
               
                ImageWorker iw = new ImageWorker(raster);
                iw.setRenderingHints(localHints);
                iw.affine(finalRaster2Model, interpolation, request.getBackgroundValues());
        return new GranuleLoadingResult(iw.getRenderedImage(), null, granuleUrl, doFiltering, pamDataset);
      }
   
    } catch (IllegalStateException e) {
      if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
        LOGGER.log(java.util.logging.Level.WARNING, new StringBuilder("Unable to load raster for granuleDescriptor ")
        .append(this.toString()).append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString(), e);
      }
      return null;
    } catch (org.opengis.referencing.operation.NoninvertibleTransformException e) {
                    if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
                        LOGGER.log(java.util.logging.Level.WARNING, new StringBuilder("Unable to load raster for granuleDescriptor ")
                        .append(this.toString()).append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString(), e);
                    }
      return null;
    } catch (TransformException e) {
                    if (LOGGER.isLoggable(java.util.logging.Level.WARNING)) {
                        LOGGER.log(java.util.logging.Level.WARNING, new StringBuilder("Unable to load raster for granuleDescriptor ")
                        .append(this.toString()).append(" with request ").append(request.toString()).append(" Resulting in no granule loaded: Empty result").toString(), e);
                    }
      return null;

                } finally {
                    try {
                        if (request.getReadType() != ReadType.JAI_IMAGEREAD && inStream != null) {
                            inStream.close();
                        }
                    } finally {
                        if (request.getReadType() != ReadType.JAI_IMAGEREAD && reader != null) {
                            reader.dispose();
                        }
                    }
                }
            }

  private GranuleOverviewLevelDescriptor getLevel(final int index, final ImageReader reader) {

    if(reader==null)
      throw new NullPointerException("Null reader passed to the internal GranuleOverviewLevelDescriptor method");   
    synchronized (granuleLevels) {
      if(granuleLevels.containsKey(Integer.valueOf(index)))
        return granuleLevels.get(Integer.valueOf(index));
      else
      {
        //load level
        // create the base grid to world transformation
        try {
          //
          //get info about the raster we have to read
          //
         
          //get selected level and base level dimensions
          final Rectangle levelDimension = Utils.getDimension(index, reader);
         
          final GranuleOverviewLevelDescriptor baseLevel= granuleLevels.get(0);
          final double scaleX=baseLevel.width/(1.0*levelDimension.width);
          final double scaleY=baseLevel.height/(1.0*levelDimension.height);
         
          // add the base level
          final GranuleOverviewLevelDescriptor newLevel=new GranuleOverviewLevelDescriptor(scaleX,scaleY,levelDimension.width,levelDimension.height);
          this.granuleLevels.put(Integer.valueOf(index),newLevel);
         
          return newLevel;
         

        } catch (IllegalStateException e) {
          throw new IllegalArgumentException(e);
         
        } catch (IOException e) {
          throw new IllegalArgumentException(e);
        }

      }     
    }
  }

  GranuleOverviewLevelDescriptor getLevel(final int index) {
   
      //load level
      // create the base grid to world transformation
      ImageInputStream inStream=null;
      ImageReader reader=null;
      try {
       
        // get a stream
              assert cachedStreamSPI!=null:"no cachedStreamSPI available!";
              inStream = cachedStreamSPI.createInputStreamInstance(granuleUrl, ImageIO.getUseCache(), ImageIO.getCacheDirectory());
        if(inStream==null)
          throw new IllegalArgumentException("Unable to create an inputstream for the granuleurl:"+(granuleUrl!=null?granuleUrl:"null"));
   
        // get a reader and try to cache the relevant SPI
        if(cachedReaderSPI==null){
          reader = ImageIOExt.getImageioReader( inStream);
          if(reader!=null)
            cachedReaderSPI=reader.getOriginatingProvider();
        }
        else
          reader=cachedReaderSPI.createReaderInstance();
        if(reader==null)
          throw new IllegalArgumentException("Unable to get an ImageReader for the provided file "+granuleUrl.toString());
        final boolean ignoreMetadata = customizeReaderInitialization(reader, null);
        reader.setInput(inStream, false, ignoreMetadata);
       
        // call internal method which will close everything
        return getLevel(index, reader);

      } catch (IllegalStateException e) {
       
        // clean up
        try{
          if(inStream!=null)
            inStream.close();
        }
        catch (Throwable ee) {
         
        }
        finally{
          if(reader!=null)
            reader.dispose();
        }
       
        throw new IllegalArgumentException(e);
       
      } catch (IOException e) {
       
        // clean up
        try{
          if(inStream!=null)
            inStream.close();
        }
        catch (Throwable ee) {
        }
        finally{
          if(reader!=null)
            reader.dispose();
        }
       
        throw new IllegalArgumentException(e);
      }
  }

  @Override
  public String toString() {
    // build a decent representation for this level
    final StringBuilder buffer = new StringBuilder();
    buffer.append("Description of a granuleDescriptor ").append("\n");
    buffer.append("BBOX:\t\t").append(granuleBBOX.toString()).append("\n");
    buffer.append("file:\t\t").append(granuleUrl).append("\n");
    buffer.append("gridToWorld:\t\t").append(baseGridToWorld).append("\n");
    int i=1;
    for(final GranuleOverviewLevelDescriptor granuleOverviewLevelDescriptor : granuleLevels.values())
    {
      i++;
      buffer.append("Description of level ").append(i++).append("\n");
      buffer.append(granuleOverviewLevelDescriptor.toString()).append("\n");
    }
    return buffer.toString();
  }

  public BoundingBox getGranuleBBOX() {
    return granuleBBOX;
  }

  public URL getGranuleUrl() {
    return granuleUrl;
  }

  public SimpleFeature getOriginator() {
    return originator;
  }

    public Geometry getFootprint() {
        if(roiProvider == null) {
            return null;
        } else {
            return roiProvider.getFootprint();
        }
    }

}
TOP

Related Classes of org.geotools.gce.imagemosaic.GranuleDescriptor$GranuleLoadingResult

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.