/*
* 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.Dimension;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.ColorModel;
import java.awt.image.IndexColorModel;
import java.awt.image.MultiPixelPackedSampleModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReadParam;
import javax.imageio.ImageReader;
import javax.media.jai.ImageLayout;
import javax.media.jai.JAI;
import javax.media.jai.ParameterBlockJAI;
import javax.media.jai.PlanarImage;
import javax.media.jai.ROI;
import javax.media.jai.ROIShape;
import javax.media.jai.operator.ConstantDescriptor;
import javax.media.jai.operator.MosaicDescriptor;
import org.apache.commons.io.FilenameUtils;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.coverage.grid.GeneralGridEnvelope;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.factory.Hints;
import org.geotools.gce.imagemosaic.RasterManager.OverviewLevel;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.opengis.coverage.ColorInterpretation;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.geometry.BoundingBox;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;
import com.sun.media.jai.codecimpl.util.ImagingException;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.index.ItemVisitor;
/**
* A RasterLayerResponse. An instance of this class is produced everytime a
* requestCoverage is called to a reader.
*
* @author Daniele Romagnoli, GeoSolutions
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
*/
@SuppressWarnings("deprecation")
class RasterLayerResponse{
/**
* Simple plcaeholder class to store the result of
*
* @author Simone Giannecchini, S.A.S.
*
*/
static class GranuleLoadingResult {
RenderedImage loadedImage;
ROIShape inclusionArea;
public ROIShape getFootprint() {
return inclusionArea;
}
public RenderedImage getRaster() {
return loadedImage;
}
GranuleLoadingResult(RenderedImage loadedImage, ROIShape inclusionArea) {
this.loadedImage = loadedImage;
this.inclusionArea = inclusionArea;
}
}
/**
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
class GranuleLoader implements Callable<GranuleLoadingResult>{
final ReferencedEnvelope cropBBox;
final MathTransform2D mosaicWorldToGrid;
final Granule granule;
final ImageReadParam readParameters;
final int imageIndex;
final Dimension tilesDimension;
GranuleLoader(
final ImageReadParam readParameters,
final int imageIndex,
final ReferencedEnvelope cropBBox,
final MathTransform2D mosaicWorldToGrid,
final Granule granule,
final Dimension tilesDimension) {
this.readParameters = ImageMosaicUtils.cloneImageReadParam(readParameters);
this.imageIndex = imageIndex;
this.cropBBox = cropBBox;
this.mosaicWorldToGrid = mosaicWorldToGrid;
this.granule = granule;
this.tilesDimension= tilesDimension!=null?(Dimension) tilesDimension.clone():null;
}
public BoundingBox getCropBBox() {
return cropBBox;
}
public MathTransform2D getMosaicWorldToGrid() {
return mosaicWorldToGrid;
}
public Granule getGranule() {
return granule;
}
public ImageReadParam getReadParameters() {
return readParameters;
}
public int getImageIndex() {
return imageIndex;
}
public GranuleLoadingResult call() throws Exception {
return granule.loadRaster(readParameters, imageIndex, cropBBox, mosaicWorldToGrid, request,tilesDimension);
}
}
class GranuleIndexVisitor implements ItemVisitor{
final URL inputURL;
/**
* Default {@link Constructor}
* @param inputURL
*/
public GranuleIndexVisitor(URL inputURL) {
this.inputURL = inputURL;
}
private final List<Future<GranuleLoadingResult>> tasks= new ArrayList<Future<GranuleLoadingResult>>();
private int granulesNumber;
private boolean doInputTransparency;
private List<ROI> rois = new ArrayList<ROI>();
private Color inputTransparentColor;
private PlanarImage[] alphaChannels;
public void visitItem(final Object item) {
// Get location and envelope of the image to load.
final SimpleFeature feature = (SimpleFeature) item;
final String granuleLocation = (String) feature.getAttribute(rasterManager.getLocationAttribute());
Geometry inclusionGeometry = null;
final ReferencedEnvelope granuleBBox = ReferencedEnvelope.reference(feature.getBounds());
// Load a granule from disk as requested.
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("About to read image number " + granulesNumber);
// If the granule is not there, dump a message and continue
final URL rasterFile = rasterManager.getPathType().resolvePath(parentLocation, granuleLocation);
if (rasterFile == null) {
if (LOGGER.isLoggable(Level.INFO))
LOGGER.info("File not found: "+granuleLocation);
return;
}
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("File found "+granuleLocation);
// granule cache
Granule granule=null;
synchronized (rasterManager.granulesCache) {
// Comment by Stefan Krueger
// TODO Before the File.toURI().toString was jused as the cache key. For URL that potentially throws an URISystaxException and i used just toString()
// granule's information are cached
if (rasterManager.granulesCache.containsKey(rasterFile.toString())) {
granule=rasterManager.granulesCache.get(rasterFile.toString());
inclusionGeometry = granule.inclusionGeometry;
} else {
// granule's information are NOT cached
//get footprint first, can be null in case we don't have any geometry
String featureID=feature.getID();
featureID=FilenameUtils.getExtension(featureID);
inclusionGeometry=rasterManager.getGranuleFootprint(featureID);
granule=new Granule(granuleBBox,rasterFile,rasterManager.parent.suggestedSPI, inclusionGeometry);
rasterManager.granulesCache.put(rasterFile.toString(),granule);
}
}
//
// load raster data
//
//create a granule loader
final Geometry bb = JTS.toGeometry((BoundingBox)mosaicBBox);
if (!footprintManagement || inclusionGeometry == null || footprintManagement && inclusionGeometry.intersects(bb)){
final GranuleLoader loader = new GranuleLoader(baseReadParameters, imageChoice, mosaicBBox, finalWorldToGridCorner, granule, request.getTileDimensions());
if(!multithreadingAllowed)
tasks.add(new FutureTask<GranuleLoadingResult>(loader));
else
tasks.add(ImageMosaicReader.multiThreadedLoader.submit(loader));
granulesNumber++;
}
if(granulesNumber>request.getMaximumNumberOfGranules())
throw new IllegalStateException("The maximum number of allowed granules ("+request.getMaximumNumberOfGranules()+")has been exceeded.");
}
public void produce(){
// reusable parameters
alphaChannels = new PlanarImage[granulesNumber];
int granuleIndex=0;
inputTransparentColor = request.getInputTransparentColor();
doInputTransparency = inputTransparentColor != null&&!footprintManagement;
// execute them all
boolean firstGranule=true;
int[] alphaIndex=null;
for (Future<GranuleLoadingResult> future :tasks) {
final RenderedImage loadedImage;
final GranuleLoadingResult result;
try {
if(!multithreadingAllowed) {
//run the loading in this thread
final FutureTask<GranuleLoadingResult> task=(FutureTask<GranuleLoadingResult>) future;
task.run();
}
result = future.get();
if (result == null) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,"Unable to load the raster for granule " +granuleIndex+ " with request "+request.toString());
continue;
}
loadedImage = result.getRaster();
if (loadedImage == null) {
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,"Unable to load the raster for granule " +granuleIndex+ " with request "+request.toString());
continue;
}
if (firstGranule) {
//
// We check here if the images have an alpha channel or some
// other sort of transparency. In case we have transparency
// I also save the index of the transparent channel.
//
// Specifically, I have to check if the loaded image have
// transparency, because if we do a ROI and/or we have a
// transparent color to set we have to remove it.
//
final ColorModel cm = loadedImage.getColorModel();
alphaIn = cm.hasAlpha();
if (alphaIn||doInputTransparency)
alphaIndex = new int[] { cm.getNumComponents() - 1 };
//
// we set the input threshold accordingly to the input
// image data type. I find the default value (which is 0) very bad
// for data type other than byte and ushort. With float and double
// it can cut off a large par of the dynamic.
//
// if(!Double.isNaN(request.getThreshold()))
// pbjMosaic.setParameter("sourceThreshold", new double[][]{{request.getThreshold()}});
// else
pbjMosaic.setParameter("sourceThreshold",
new double[][] { { ImageMosaicUtils.getThreshold(loadedImage.getSampleModel().getDataType()) } });
firstGranule=false;
}
} catch (InterruptedException e) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE,"Unable to load the raster for granule " +granuleIndex,e);
continue;
} catch (ExecutionException e) {
if(LOGGER.isLoggable(Level.SEVERE))
LOGGER.log(Level.SEVERE,"Unable to load the raster for granule " +granuleIndex,e);
continue;
} catch (ImagingException e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Adding to mosaic image number " + granuleIndex+ " failed, original request was "+request);
continue;
} catch (javax.media.jai.util.ImagingException e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Adding to mosaic image number " + granuleIndex+ " failed, original request was "+request);
continue;
}
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Adding to mosaic image number " + granuleIndex);
//
// add to the mosaic collection, with preprocessing
//
final RenderedImage raster = processGranuleRaster(
loadedImage,
granuleIndex,
alphaIndex,
alphaIn,
alphaChannels,
doInputTransparency,
inputTransparentColor);
// we need to add its roi in order to avoid problems whith the mosaic overl
ROI imageBounds = new ROIShape(PlanarImage.wrapRenderedImage(raster).getBounds());
if (footprintManagement){
final ROIShape footprint = result.getFootprint();
imageBounds = footprint != null? imageBounds.intersect(footprint): imageBounds;
}
rois.add(imageBounds);
// add to mosaic
pbjMosaic.addSource(raster);
//increment index
granuleIndex++;
}
granulesNumber = granuleIndex;
if (granulesNumber == 0) {
if (LOGGER.isLoggable(Level.FINE)) {
LOGGER.log(Level.FINE,"Unable to load any granule ");
}
return;
}
//
// management of the alpha information which
// can be the result of a masking operation upon the request for a
// transparent color or the result of input images with internal
// transparency.
if (alphaIn || doInputTransparency) {
// //
//
// In case the input images have transparency information
// this
// way we can handle it.
//
// //
pbjMosaic.setParameter("sourceAlpha", alphaChannels);
}
pbjMosaic.setParameter("sourceROI", rois.toArray(new ROI[rois.size()]));
}
}
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(RasterLayerResponse.class);
/**
* The GridCoverage produced after a {@link #compute()} method call
*/
private GridCoverage2D gridCoverage;
/** The {@link RasterLayerRequest} originating this response */
private RasterLayerRequest request;
/** The coverage factory producing a {@link GridCoverage} from an image */
private GridCoverageFactory coverageFactory;
/** The base envelope related to the input coverage */
private GeneralEnvelope coverageEnvelope;
private URL inputURL;
private boolean frozen = false;
private RasterManager rasterManager;
private String parentLocation;
private Color finalTransparentColor;
private ParameterBlockJAI pbjMosaic;
private ReferencedEnvelope mosaicBBox;
private Rectangle rasterBounds;
private MathTransform2D finalGridToWorldCorner;
private MathTransform2D finalWorldToGridCorner;
private int imageChoice=0;
private ImageReadParam baseReadParameters= new ImageReadParam();
private boolean multithreadingAllowed=false;
private boolean footprintManagement = !ImageMosaicUtils.IGNORE_FOOTPRINT;
private boolean alphaIn=false;
private MathTransform baseGridToWorld;
private double[] backgroundValues;
/**
* Construct a {@code RasterLayerResponse} given a specific
* {@link RasterLayerRequest}, a {@code GridCoverageFactory} to produce
* {@code GridCoverage}s and an {@code ImageReaderSpi} to be used for
* instantiating an Image Reader for a read operation,
*
* @param request
* a {@link RasterLayerRequest} originating this response.
* @param coverageFactory
* a {@code GridCoverageFactory} to produce a {@code
* GridCoverage} when calling the {@link #compute()} method.
* @param readerSpi
* the Image Reader Service provider interface.
*/
public RasterLayerResponse(final RasterLayerRequest request,
final RasterManager rasterManager) {
this.request = request;
inputURL = rasterManager.getInputURL();
try {
parentLocation = DataUtilities.getParentUrl(inputURL).toExternalForm();
} catch (MalformedURLException e) {
throw new IllegalArgumentException("Unable to determine the parent location of "+inputURL, e);
}
coverageEnvelope = rasterManager.getCoverageEnvelope();
this.coverageFactory = rasterManager.getCoverageFactory();
this.rasterManager = rasterManager;
baseGridToWorld=rasterManager.getRaster2Model();
finalTransparentColor=request.getOutputTransparentColor();
// are we doing multithreading?
multithreadingAllowed= request.isMultithreadingAllowed();
footprintManagement = request.isFootprintManagement();
backgroundValues = request.getBackgroundValues();
}
/**
* Compute the coverage request and produce a grid coverage which will be
* returned by {@link #createResponse()}. The produced grid coverage may be
* {@code null} in case of empty request.
*
* @return the {@link GridCoverage} produced as computation of this response
* using the {@link #compute()} method.
* @throws IOException
* @uml.property name="gridCoverage"
*/
public GridCoverage2D createResponse() throws IOException {
processRequest();
return gridCoverage;
}
/**
* @return the {@link RasterLayerRequest} originating this response.
*
* @uml.property name="request"
*/
public RasterLayerRequest getOriginatingCoverageRequest() {
return request;
}
/**
* This method creates the GridCoverage2D from the underlying file given a
* specified envelope, and a requested dimension.
*
* @param iUseJAI
* specify if the underlying read process should leverage on a
* JAI ImageRead operation or a simple direct call to the {@code
* read} method of a proper {@code ImageReader}.
* @param overviewPolicy
* the overview policy which need to be adopted
* @return a {@code GridCoverage}
*
* @throws java.io.IOException
*/
private void processRequest() throws IOException {
if (request.isEmpty())
{
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,"Request is empty: "+request.toString());
this.gridCoverage=null;
return;
}
if (frozen)
return;
// assemble granules
final RenderedImage mosaic = prepareResponse();
//postproc
RenderedImage finalRaster = postProcessRaster(mosaic);
//create the coverage
gridCoverage=prepareCoverage(finalRaster);
//freeze
frozen = true;
}
private RenderedImage postProcessRaster(RenderedImage mosaic) {
// alpha on the final mosaic
if (finalTransparentColor != null) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Support for alpha on final mosaic");
return ImageMosaicUtils.makeColorTransparent(finalTransparentColor,mosaic);
}
return mosaic;
}
/**
* This method loads the granules which overlap the requested
* {@link GeneralEnvelope} using the provided values for alpha and input
* ROI.
* @return
* @throws DataSourceException
*/
private RenderedImage prepareResponse() throws DataSourceException {
try {
//
// prepare the params for executing a mosaic operation.
//
pbjMosaic = new ParameterBlockJAI("Mosaic");
pbjMosaic.setParameter("backgroundValues", backgroundValues);
// It might important to set the mosaic type to blend otherwise
// sometimes strange results jump in.
if (request.isBlend())
pbjMosaic.setParameter("mosaicType",MosaicDescriptor.MOSAIC_TYPE_BLEND);
else
pbjMosaic.setParameter("mosaicType",MosaicDescriptor.MOSAIC_TYPE_OVERLAY);
// select the relevant overview, notice that at this time we have
// relaxed a bit the requirement to have the same exact resolution
// for all the overviews, but still we do not allow for reading the
// various grid to world transform directly from the input files,
// therefore we are assuming that each granule has a scale and
// translate only grid to world that can be deduced from its base
// level dimension and envelope. The grid to world transforms for
// the other levels can be computed accordingly knowning the scale
// factors.
if (request.getRequestedBBox() != null&& request.getRequestedRasterArea() != null)
imageChoice = setReadParams(request.getOverviewPolicy(), baseReadParameters, request);
else
imageChoice = 0;
assert imageChoice>=0;
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine(new StringBuffer("Loading level ").append(
imageChoice).append(" with subsampling factors ")
.append(baseReadParameters.getSourceXSubsampling()).append(" ")
.append(baseReadParameters.getSourceYSubsampling()).toString());
// ok we got something to return, let's load records from the index
final BoundingBox cropBBOX = request.getCropBBox();
if (cropBBOX != null)
mosaicBBox = ReferencedEnvelope.reference(cropBBOX);
else
mosaicBBox = new ReferencedEnvelope(coverageEnvelope);
//compute final world to grid
// base grid to world for the center of pixels
final AffineTransform g2w = new AffineTransform((AffineTransform) baseGridToWorld);
// move it to the corner
g2w.concatenate(ImageMosaicUtils.CENTER_TO_CORNER);
//keep into account overviews and subsampling
final OverviewLevel level = rasterManager.overviewsController.resolutionsLevels.get(imageChoice);
final OverviewLevel baseLevel = rasterManager.overviewsController.resolutionsLevels.get(0);
final AffineTransform2D adjustments = new AffineTransform2D(
(level.resolutionX/baseLevel.resolutionX)*baseReadParameters.getSourceXSubsampling(),
0,
0,
(level.resolutionY/baseLevel.resolutionY)*baseReadParameters.getSourceYSubsampling(),
0,
0);
g2w.concatenate(adjustments);
finalGridToWorldCorner=new AffineTransform2D(g2w);
finalWorldToGridCorner = finalGridToWorldCorner.inverse();// compute raster bounds
rasterBounds=new GeneralGridEnvelope(CRS.transform(finalWorldToGridCorner, mosaicBBox),PixelInCell.CELL_CORNER,false).toRectangle();
if (rasterBounds.width == 0)
rasterBounds.width++;
if (rasterBounds.height == 0)
rasterBounds.height++;
// create the index visitor and visit the feature
final GranuleIndexVisitor visitor = new GranuleIndexVisitor(inputURL);
rasterManager.getFeaturesFromIndex(mosaicBBox, visitor);
visitor.produce();
//
// Did we actually load anything?? Notice that it might happen that
// either we have holes inside the definition area for the mosaic
// or we had some problem with missing tiles, therefore it might
// happen that for some bboxes we don't have anything to load.
//
if (visitor.granulesNumber>=1) {
//
// Create the mosaic image by doing a crop if necessary and also
// managing the transparent color if applicable. Be aware that
// management of the transparent color involves removing
// transparency information from the input images.
//
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine(new StringBuilder("Loaded bbox ").append(
mosaicBBox.toString()).append(" while crop bbox ")
.append(request.getCropBBox().toString())
.toString());
return buildMosaic();
} else {
// if we get here that means that we do not have anything to load
// but still we are inside the definition area for the mosaic,
// therefore we create a fake coverage using the background values,
// if provided (defaulting to 0), as well as the compute raster
// bounds, envelope and grid to world.
Number[] values = ImageMosaicUtils.getBackgroundValues(rasterManager.defaultSM, backgroundValues);
return ConstantDescriptor.create(
Float.valueOf(rasterBounds.width),
Float.valueOf(rasterBounds.height),
values,
rasterManager.defaultImageLayout!=null?new RenderingHints(JAI.KEY_IMAGE_LAYOUT,rasterManager.defaultImageLayout):null);
}
} catch (IOException e) {
throw new DataSourceException("Unable to create this mosaic", e);
} catch (TransformException e) {
throw new DataSourceException("Unable to create this mosaic", e);
}
}
private RenderedImage processGranuleRaster(
RenderedImage granule,
final int granuleIndex,
final int[] alphaIndex,
final boolean alphaIn,
final PlanarImage[] alphaChannels,
final boolean doTransparentColor, final Color transparentColor) {
//
// INDEX COLOR MODEL EXPANSION
//
// Take into account the need for an expansions of the original color
// model.
//
// If the original color model is an index color model an expansion
// might be requested in case the different palettes are not all the
// same. In this case the mosaic operator from JAI would provide wrong
// results since it would take the first palette and use that one for
// all the other images.
//
// There is a special case to take into account here. In case the input
// images use an IndexColorModel it might happen that the transparent
// color is present in some of them while it is not present in some
// others. This case is the case where for sure a color expansion is
// needed. However we have to take into account that during the masking
// phase the images where the requested transparent color was present
// will have 4 bands, the other 3. If we want the mosaic to work we
// have to add an extra band to the latter type of images for providing
// alpha information to them.
//
//
if (rasterManager.expandMe && granule.getColorModel() instanceof IndexColorModel) {
granule = new ImageWorker(granule).forceComponentColorModel().getRenderedImage();
}
//
// TRANSPARENT COLOR MANAGEMENT
//
if (doTransparentColor) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Support for alpha on input image number "+ granuleIndex);
granule = ImageMosaicUtils.makeColorTransparent(transparentColor, granule);
alphaIndex[0]= granule.getColorModel().getNumComponents() - 1 ;
}
//
// ROI
//
if (alphaIn || doTransparentColor) {
ImageWorker w = new ImageWorker(granule);
if (granule.getSampleModel() instanceof MultiPixelPackedSampleModel)
w.forceComponentColorModel();
//
// ALPHA in INPUT
//
// I have to select the alpha band and provide it to the final
// mosaic operator. I have to force going to ComponentColorModel in
// case the image is indexed.
//
if (granule.getColorModel() instanceof IndexColorModel) {
alphaChannels[granuleIndex] = w.forceComponentColorModel().retainLastBand().getPlanarImage();
} else
alphaChannels[granuleIndex] = w.retainBands(alphaIndex).getPlanarImage();
}
return granule;
}
/**
* Once we reach this method it means that we have loaded all the images
* which were intersecting the requested envelope. Next step is to create
* the final mosaic image and cropping it to the exact requested envelope.
*
* @return A {@link RenderedImage}}.
*/
private RenderedImage buildMosaic() throws IOException {
final ImageLayout layout = new ImageLayout(
rasterBounds.x,
rasterBounds.y,
rasterBounds.width,
rasterBounds.height);
//tiling
final Dimension tileDimensions=request.getTileDimensions();
if(tileDimensions!=null)
layout.setTileHeight(tileDimensions.width).setTileWidth(tileDimensions.height);
final RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout);
RenderedImage mosaic = JAI.create("Mosaic", pbjMosaic, hints);
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine(new StringBuffer("Mosaic created ").toString());
// create the coverage
return mosaic;
}
private GridCoverage2D prepareCoverage(RenderedImage image) throws IOException {
// creating bands
final SampleModel sm=image.getSampleModel();
final ColorModel cm=image.getColorModel();
final int numBands = sm.getNumBands();
final GridSampleDimension[] bands = new GridSampleDimension[numBands];
// setting bands names.
for (int i = 0; i < numBands; i++) {
final ColorInterpretation colorInterpretation=TypeMap.getColorInterpretation(cm, i);
if(colorInterpretation==null)
throw new IOException("Unrecognized sample dimension type");
bands[i] = new GridSampleDimension(colorInterpretation.name()).geophysics(true);
}
return coverageFactory.create(rasterManager.getCoverageIdentifier(), image,new GeneralEnvelope(mosaicBBox), bands, null, null);
}
/**
* This method is responsible for preparing the read param for doing an
* {@link ImageReader#read(int, ImageReadParam)}.
*
*
* <p>
* This method is responsible for preparing the read param for doing an
* {@link ImageReader#read(int, ImageReadParam)}. It sets the passed
* {@link ImageReadParam} in terms of decimation on reading using the
* provided requestedEnvelope and requestedDim to evaluate the needed
* resolution. It also returns and {@link Integer} representing the index of
* the raster to be read when dealing with multipage raster.
*
* @param overviewPolicy
* it can be one of {@link Hints#VALUE_OVERVIEW_POLICY_IGNORE},
* {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST},
* {@link Hints#VALUE_OVERVIEW_POLICY_QUALITY} or
* {@link Hints#VALUE_OVERVIEW_POLICY_SPEED}. It specifies the
* policy to compute the overviews level upon request.
* @param readParams
* an instance of {@link ImageReadParam} for setting the
* subsampling factors.
* @param requestedEnvelope
* the {@link GeneralEnvelope} we are requesting.
* @param requestedDim
* the requested dimensions.
* @return the index of the raster to read in the underlying data source.
* @throws IOException
* @throws TransformException
*/
private int setReadParams(final OverviewPolicy overviewPolicy,
final ImageReadParam readParams, final RasterLayerRequest request)
throws IOException, TransformException {
// Default image index 0
int imageChoice = 0;
// default values for subsampling
readParams.setSourceSubsampling(1, 1, 0, 0);
//
// Init overview policy
//
// //
// when policy is explictly provided it overrides the policy provided
// using hints.
final OverviewPolicy policy;
if (overviewPolicy == null)
policy = rasterManager.overviewPolicy;
else
policy = overviewPolicy;
// requested to ignore overviews
if (policy.equals(OverviewPolicy.IGNORE))
return imageChoice;
// overviews and decimation
imageChoice = rasterManager.overviewsController.pickOverviewLevel(overviewPolicy, request);
// DECIMATION ON READING
rasterManager.decimationController.performDecimation(imageChoice, readParams, request);
return imageChoice;
}
}