/*
* 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.Rectangle;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReadParam;
import javax.media.jai.ImageLayout;
import org.apache.commons.io.FilenameUtils;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.io.OverviewPolicy;
import org.geotools.data.DataSourceException;
import org.geotools.data.DataUtilities;
import org.geotools.data.FeatureSource;
import org.geotools.data.shapefile.ShapefileDataStore;
import org.geotools.factory.Hints;
import org.geotools.feature.FeatureCollection;
import org.geotools.feature.FeatureIterator;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.metadata.iso.extent.GeographicBoundingBoxImpl;
import org.geotools.referencing.CRS;
import org.geotools.util.SoftValueHashMap;
import org.geotools.util.Utilities;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.feature.Feature;
import org.opengis.feature.simple.SimpleFeature;
import org.opengis.feature.simple.SimpleFeatureType;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
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.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.index.ItemVisitor;
import com.vividsolutions.jts.index.SpatialIndex;
import com.vividsolutions.jts.index.strtree.STRtree;
class RasterManager {
/** Logger. */
private final static Logger LOGGER = org.geotools.util.logging.Logging.getLogger(RasterManager.class);
final SoftValueHashMap<String, Granule> granulesCache= new SoftValueHashMap<String, Granule>();
/**
* This class simply builds an SRTREE spatial granulesIndex in memory for fast indexed
* geometric queries.
*
* <p>
* Since the {@link ImageMosaicReader} heavily uses spatial queries to find out
* which are the involved tiles during mosaic creation, it is better to do some
* caching and keep the granulesIndex in memory as much as possible, hence we came up
* with this granulesIndex.
*
* @author Simone Giannecchini, S.A.S.
* @author Stefan Alfons Krueger (alfonx), Wikisquare.de : Support for jar:file:foo.jar/bar.properties URLs
* @since 2.5
*
* @source $URL$
*/
class GranuleCatalog {
private long lastModifiedGranuleIndex=-1;
private long lastModifiedFootprintIndex=-1;
/** The {@link STRtree} granulesIndex. */
private SoftReference<STRtree> granulesIndex= new SoftReference<STRtree>(null);
private SoftReference<Map<String, Geometry>> footprintsIndex = new SoftReference<Map<String, Geometry>>(null);
/**
* Constructs a {@link GranuleCatalog} out of a {@link FeatureCollection}.
*
* @param features
* @throws IOException
*/
private synchronized SpatialIndex getIndex() throws IOException {
STRtree tree = null;
if (parent.sourceURL.getProtocol().equals("file")) {
FileLock lock = null;
FileChannel channel = null;
try {
// Get a file channel for the file
File file = DataUtilities.urlToFile(parent.sourceURL);
if(file.canWrite()){
channel = new RandomAccessFile(file, "rw").getChannel();
// Create a shared lock on the file.
// This method blocks until it can retrieve the lock.
lock = channel.lock(0, Long.MAX_VALUE, true);
}
// now check the modified time and rebuild the granulesIndex as
// needed
final long lastMod = file.lastModified();
if (lastMod > this.lastModifiedGranuleIndex) {
// the underlying files has been modified, let's clean
// up the granulesIndex
granulesIndex.clear();
tree = null;
} else
tree = granulesIndex.get();
if (tree == null) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Index needs to be recreated...");
createIndex();
tree = granulesIndex.get();
assert tree != null;
} else if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Index does not need to be created...");
return tree;
} finally {
try {
if (lock != null)
// Release the lock
lock.release();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
} finally {
lock = null;
}
try {
if (channel != null)
// Close the file
channel.close();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
} finally {
channel = null;
}
}
} else {
// The URL is not a file. So no locks are needed, and no granulesIndex
// can be created. Use an granulesIndex if it is there.
tree = granulesIndex.get();
if (tree == null) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("No granulesIndex exits and we create a new one.");
createIndex();
tree = granulesIndex.get();
} else if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Index does not need to be created...");
return tree;
}
}
/**
* This method shall only be called when the <code>parent.sourceURL</code> is of protocol <code>file:</code>
*/
private void createIndex() {
// check that we holds a lock
assert Thread.holdsLock(this);
ShapefileDataStore tileIndexStore=null;
FeatureIterator<SimpleFeature> it=null;
FeatureCollection<SimpleFeatureType, SimpleFeature> features=null;
//
// Load tiles informations, especially the bounds, which will be
// reused
//
try{
// creating a store
tileIndexStore = new ShapefileDataStore(parent.sourceURL);
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Connected mosaic reader to its data store "
+ parent.sourceURL.toString());
final String[] typeNames = tileIndexStore.getTypeNames();
if (typeNames.length <= 0)
throw new IllegalArgumentException(
"Problems when opening the granulesIndex, no typenames for the schema are defined");
// loading all the features into memory to build an in-memory granulesIndex.
String typeName = typeNames[0];
final FeatureSource<SimpleFeatureType, SimpleFeature> featureSource = tileIndexStore.getFeatureSource(typeName);
if (featureSource == null)
throw new NullPointerException(
"The provided FeatureSource<SimpleFeatureType, SimpleFeature> is null, it's impossible to create an granulesIndex!");
features = featureSource.getFeatures();
if (features == null)
throw new NullPointerException(
"The provided FeatureCollection<SimpleFeatureType, SimpleFeature> is null, it's impossible to create an granulesIndex!");
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Index Loaded");
//load the feature from the shapefile and create JTS granulesIndex
it = features.features();
if (!it.hasNext())
throw new IllegalArgumentException(
"The provided FeatureCollection<SimpleFeatureType, SimpleFeature> or empty, it's impossible to create an granulesIndex!");
// now build the granulesIndex
STRtree tree = new STRtree();
while (it.hasNext()) {
final SimpleFeature feature = it.next();
final Geometry g = (Geometry) feature.getDefaultGeometry();
tree.insert(g.getEnvelopeInternal(), feature);
}
// force granulesIndex construction --> STRTrees are build on first call to
// query
tree.build();
// save the soft reference
granulesIndex= new SoftReference<STRtree>(tree);
// IF this the sourceURL points to a File, THEN we are using the
// last modified time to determine whether we have to recreate the
// granulesIndex. Otherwise now used.
if(parent.sourceURL.getProtocol().equals("file")) {
this.lastModifiedGranuleIndex = DataUtilities.urlToFile(parent.sourceURL).lastModified();
} else {
this.lastModifiedGranuleIndex = new Date().getTime();
}
}
catch (Throwable e) {
throw new IllegalArgumentException(e);
}
finally{
try {
if(tileIndexStore!=null)
tileIndexStore.dispose();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
}
finally{
tileIndexStore=null;
}
if(it!=null){
// closing he iterator to free some resources.
if(features!=null)
features.close(it);
it.close();
}
}
}
/**
* Finds the features that intersects the provided {@link Envelope}:
*
* @param envelope
* The {@link Envelope} to test for intersection.
* @return List of {@link Feature} that intersect the providede
* {@link Envelope}.
* @throws IOException
*/
@SuppressWarnings("unchecked")
public List<SimpleFeature> findFeatures(final Envelope envelope) throws IOException {
Utilities.ensureNonNull("envelope",envelope);
return getIndex().query(envelope);
}
/**
* Finds the features that intersects the provided {@link Envelope}:
*
* @param envelope
* The {@link Envelope} to test for intersection.
* @return List of {@link Feature} that intersect the providede
* {@link Envelope}.
* @throws IOException
*/
public void findFeatures(final Envelope envelope, final ItemVisitor visitor) throws IOException {
Utilities.ensureNonNull("envelope",envelope);
Utilities.ensureNonNull("visitor",visitor);
getIndex().query(envelope, visitor);
}
public synchronized void dispose()throws IOException{
granulesIndex.clear();
}
public Geometry getFootprint(String id) {
Utilities.ensureNonNull("id",id);
Map<String, Geometry> cache;
try {
cache = getFootprintsIndex();
if(cache!=null&&cache.containsKey(id))
return cache.get(id);
} catch (IOException e) {
if(LOGGER.isLoggable(Level.WARNING))
LOGGER.log(Level.WARNING,e.getLocalizedMessage(),e);
}
return null;
}
private synchronized Map<String, Geometry> getFootprintsIndex() throws IOException{
Map<String, Geometry> retValue=null;
if (parent.sourceURL.getProtocol().equals("file")) {
final String fileName = DataUtilities.urlToFile(inputURL).getAbsolutePath();
final File footprintSummaryFile = new File(FilenameUtils.getFullPathNoEndSeparator(fileName),FilenameUtils.getBaseName(fileName)+FootprintUtils.FOOTPRINT_EXT);
if (!(footprintSummaryFile != null && footprintSummaryFile.exists() && footprintSummaryFile.canRead()))
return null;
FileLock lock = null;
FileChannel channel = null;
try {
// Get a file channel for the file
if(footprintSummaryFile.canWrite()){
channel = new RandomAccessFile(footprintSummaryFile, "rw").getChannel();
// Create a shared lock on the file.
// This method blocks until it can retrieve the lock.
lock = channel.lock(0, Long.MAX_VALUE, true);
}
// now check the modified time and rebuild the granulesIndex as
// needed
final long lastMod = footprintSummaryFile.lastModified();
if (lastMod > this.lastModifiedFootprintIndex) {
// the underlying files has been modified, let's clean
// up the granulesIndex
footprintsIndex.clear();
} else
retValue = footprintsIndex.get();
if (retValue == null) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("footprintsIndex needs to be recreated...");
createFootprintIndex();
retValue = footprintsIndex.get();
assert retValue != null;
} else if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("footprintsIndex does not need to be created...");
return retValue;
} finally {
try {
if (lock != null)
// Release the lock
lock.release();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
} finally {
lock = null;
}
try {
if (channel != null)
// Close the file
channel.close();
} catch (Throwable e) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE, e.getLocalizedMessage(), e);
} finally {
channel = null;
}
}
} else {
// The URL is not a file. So no locks are needed, and no granulesIndex
// can be created. Use an granulesIndex if it is there.
retValue = footprintsIndex.get();
if (retValue == null) {
if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("No footprintsIndex exists and we create a new one.");
createFootprintIndex();
retValue = footprintsIndex.get();
} else if (LOGGER.isLoggable(Level.FINE))
LOGGER.fine("footprintsIndex does not need to be created...");
return retValue;
}
}
private void createFootprintIndex() {
// check that we holds a lock
assert Thread.holdsLock(this);
final Map<String, Geometry> footprintsMap=new HashMap<String, Geometry>();
final String fileName = DataUtilities.urlToFile(inputURL).getAbsolutePath();
final File footprintSummaryFile = new File(FilenameUtils.getFullPathNoEndSeparator(fileName),FilenameUtils.getBaseName(fileName)+FootprintUtils.FOOTPRINT_EXT);
if (footprintSummaryFile != null && footprintSummaryFile.exists() && footprintSummaryFile.canRead()) {
FootprintUtils.initFootprintsGranuleIDGeometryMap(footprintSummaryFile, footprintsMap);
}
// save the soft reference
footprintsIndex= new SoftReference<Map<String, Geometry>>(footprintsMap);
// IF this the sourceURL points to a File, THEN we are using the
// last modified time to determine whether we have to recreate the
// granulesIndex. Otherwise now used.
this.lastModifiedFootprintIndex = footprintSummaryFile.lastModified();
}
}
/**
* Simple support class for sorting overview resolutions
* @author Andrea Aime
* @author Simone Giannecchini, GeoSolutions.
* @since 2.5
*/
static class OverviewLevel implements Comparable<OverviewLevel> {
double scaleFactor;
double resolutionX;
double resolutionY;
int imageChoice;
public OverviewLevel(
final double scaleFactor,
final double resolutionX,
final double resolutionY,
int imageChoice) {
this.scaleFactor = scaleFactor;
this.resolutionX=resolutionX;
this.resolutionY=resolutionY;
this.imageChoice = imageChoice;
}
public int compareTo(final OverviewLevel other) {
if(scaleFactor > other.scaleFactor)
return 1;
else if(scaleFactor < other.scaleFactor)
return -1;
else
return 0;
}
@Override
public String toString() {
return "OverviewLevel[Choice=" + imageChoice + ",scaleFactor=" + scaleFactor + "]";
}
@Override
public int hashCode() {
int hash= Utilities.hash(imageChoice, 31);
hash=Utilities.hash(resolutionX, hash);
hash=Utilities.hash(resolutionY, hash);
hash=Utilities.hash(scaleFactor, hash);
return hash;
}
}
class OverviewsController {
final ArrayList<org.geotools.gce.imagemosaic.RasterManager.OverviewLevel> resolutionsLevels = new ArrayList<OverviewLevel>();
public OverviewsController() {
// notice that we assume what follows:
// -highest resolution image is at level 0.
// -all the overviews share the same envelope
// -the aspect ratio for the overviews is constant
// -the provided resolutions are taken directly from the grid
resolutionsLevels.add(new OverviewLevel(1, highestRes[0],highestRes[1], 0));
if (numberOfOvervies > 0) {
for (int i = 0; i < overviewsResolution.length; i++)
resolutionsLevels.add(
new OverviewLevel(
overviewsResolution[i][0] / highestRes[0],
overviewsResolution[i][0],
overviewsResolution[i][1], i + 1)
);
Collections.sort(resolutionsLevels);
}
}
int pickOverviewLevel(final OverviewPolicy policy,final RasterLayerRequest request) {
// //
//
// If this file has only
// one page we use decimation, otherwise we use the best page available.
// Future versions should use both.
//
// //
if (resolutionsLevels==null||resolutionsLevels.size() <=0)
return 0;
// Now search for the best matching resolution.
// Check also for the "perfect match"... unlikely in practice unless someone
// tunes the clients to request exactly the resolution embedded in
// the overviews, something a perf sensitive person might do in fact
// requested scale factor for least reduced axis
final OverviewLevel max = (OverviewLevel) resolutionsLevels.get(0);
// the requested resolutions
final double requestedScaleFactorX;
final double requestedScaleFactorY;
final double[] requestedRes = request.getRequestedResolution();
if (requestedRes != null)
{
final double reqx = requestedRes[0];
final double reqy = requestedRes[1];
requestedScaleFactorX = reqx / max.resolutionX;
requestedScaleFactorY = reqy / max.resolutionY;
}
else
return 0;
// {
// final double[] scaleFactors = request.getRequestedRasterScaleFactors();
// if(scaleFactors==null)
// return 0;
// requestedScaleFactorX=scaleFactors[0];
// requestedScaleFactorY=scaleFactors[1];
// }
final int leastReduceAxis = requestedScaleFactorX <= requestedScaleFactorY ? 0: 1;
final double requestedScaleFactor = leastReduceAxis == 0 ? requestedScaleFactorX: requestedScaleFactorY;
// are we looking for a resolution even higher than the native one?
if(requestedScaleFactor<=1)
return max.imageChoice;
// are we looking for a resolution even lower than the smallest overview?
final OverviewLevel min = (OverviewLevel) resolutionsLevels.get(resolutionsLevels.size() - 1);
if(requestedScaleFactor>=min.scaleFactor)
return min.imageChoice;
// Ok, so we know the overview is between min and max, skip the first
// and search for an overview with a resolution lower than the one requested,
// that one and the one from the previous step will bound the searched resolution
OverviewLevel prev = max;
final int size=resolutionsLevels.size();
for (int i = 1; i <size; i++) {
final OverviewLevel curr = resolutionsLevels.get(i);
// perfect match check
if(curr.scaleFactor==requestedScaleFactor) {
return curr.imageChoice;
}
// middle check. The first part of the condition should be sufficient, but
// there are cases where the x resolution is satisfied by the lowest resolution,
// the y by the one before the lowest (so the aspect ratio of the request is
// different than the one of the overviews), and we would end up going out of the loop
// since not even the lowest can "top" the request for one axis
if(curr.scaleFactor>requestedScaleFactor|| i == size - 1) {
if(policy ==OverviewPolicy.QUALITY)
return prev.imageChoice;
else if(policy == OverviewPolicy.SPEED)
return curr.imageChoice;
else if(requestedScaleFactor - prev.scaleFactor < curr.scaleFactor - requestedScaleFactor)
return prev.imageChoice;
else
return curr.imageChoice;
}
prev = curr;
}
//fallback
return max.imageChoice;
}
}
class DecimationController {
public DecimationController() {
}
/**
* This method is responsible for evaluating possible subsampling factors
* once the best resolution level has been found, in case we have support
* for overviews, or starting from the original coverage in case there are
* no overviews available.
*
* Anyhow this method should not be called directly but subclasses should
* make use of the setReadParams method instead in order to transparently
* look for overviews.
*
* @param imageIndex
* @param readParameters
* @param requestedRes
*/
void performDecimation(
final int imageIndex,
final ImageReadParam readParameters,
final RasterLayerRequest request) {
{
// the read parameters cannot be null
Utilities.ensureNonNull("readParameters", readParameters);
Utilities.ensureNonNull("request", request);
//get the requested resolution
final double[] requestedRes=request.getRequestedResolution();
if(requestedRes==null)
{
// if there is no requested resolution we don't do any subsampling
readParameters.setSourceSubsampling(1, 1, 0, 0);
return;
}
double selectedRes[] = new double[2];
final OverviewLevel level=overviewsController.resolutionsLevels.get(imageIndex);
selectedRes[0] = level.resolutionX;
selectedRes[1] = level.resolutionY;
final int rasterWidth, rasterHeight;
if (imageIndex == 0) {
// highest resolution
rasterWidth = spatialDomainManager.coverageRasterArea.width;
rasterHeight = spatialDomainManager.coverageRasterArea.height;
} else {
// work on overviews
//TODO this is bad side effect of how the Overviews are managed right now. There are two problems here,
// first we are assuming that we are working with LON/LAT, second is that we are getting just an approximation of
// raster dimensions. The solution is to have the rater dimensions on each level and to confront raster dimensions,
//which means working
rasterWidth = (int) Math.round(spatialDomainManager.coverageBBox.getSpan(0)/ selectedRes[0]);
rasterHeight = (int) Math.round(spatialDomainManager.coverageBBox.getSpan(1)/ selectedRes[1]);
}
// /////////////////////////////////////////////////////////////////////
// DECIMATION ON READING
// Setting subsampling factors with some checks
// 1) the subsampling factors cannot be zero
// 2) the subsampling factors cannot be such that the w or h are
// zero
// /////////////////////////////////////////////////////////////////////
int subSamplingFactorX = (int) Math.floor(requestedRes[0]/ selectedRes[0]);
subSamplingFactorX = subSamplingFactorX == 0 ? 1: subSamplingFactorX;
while (rasterWidth / subSamplingFactorX <= 0 && subSamplingFactorX >= 0)
subSamplingFactorX--;
subSamplingFactorX = subSamplingFactorX <= 0 ? 1: subSamplingFactorX;
int subSamplingFactorY = (int) Math.floor(requestedRes[1]/ selectedRes[1]);
subSamplingFactorY = subSamplingFactorY == 0 ? 1: subSamplingFactorY;
while (rasterHeight / subSamplingFactorY <= 0 && subSamplingFactorY >= 0)subSamplingFactorY--;
subSamplingFactorY = subSamplingFactorY <= 0 ? 1: subSamplingFactorY;
readParameters.setSourceSubsampling(subSamplingFactorX,subSamplingFactorY, 0, 0);
}
}
}
/**
* This class is responsible for putting together all the 2D spatial information needed for a certain raster.
*
* <p>
* Notice that when this structure will be extended to work in ND this will become much more complex or as an
* alternative a sibling TemporalDomainManager will be created.
*
* @author Simone Giannecchini, GeoSolutions SAS
*
*/
class SpatialDomainManager{
public SpatialDomainManager() throws TransformException, FactoryException {
setBaseParameters();
prepareCoverageSpatialElements();
}
/** The base envelope 2D */
ReferencedEnvelope coverageBBox;
/** The CRS for the coverage */
CoordinateReferenceSystem coverageCRS;
/** The CRS related to the base envelope 2D */
CoordinateReferenceSystem coverageCRS2D;
// ////////////////////////////////////////////////////////////////////////
//
// Base coverage properties
//
// ////////////////////////////////////////////////////////////////////////
/** The base envelope read from file */
GeneralEnvelope coverageEnvelope = null;
double[] coverageFullResolution;
/** WGS84 envelope 2D for this coverage */
ReferencedEnvelope coverageGeographicBBox;
CoordinateReferenceSystem coverageGeographicCRS2D;
MathTransform2D coverageGridToWorld2D;
/** The base grid range for the coverage */
Rectangle coverageRasterArea;
/**
* Initialize the 2D properties (CRS and Envelope) of this coverage
* @throws TransformException
*
* @throws FactoryException
* @throws TransformException
* @throws FactoryException
*/
private void prepareCoverageSpatialElements() throws TransformException, FactoryException {
//
// basic initialization
//
coverageGeographicBBox =ImageMosaicUtils.getReferencedEnvelopeFromGeographicBoundingBox(new GeographicBoundingBoxImpl(coverageEnvelope));
coverageGeographicCRS2D=coverageGeographicBBox.getCoordinateReferenceSystem();
//
// Get the original envelope 2d and its spatial reference system
//
coverageCRS2D = CRS.getHorizontalCRS(coverageCRS);
assert coverageCRS2D.getCoordinateSystem().getDimension() == 2;
if (coverageCRS.getCoordinateSystem().getDimension() != 2) {
final MathTransform transform=CRS.findMathTransform(coverageCRS,(CoordinateReferenceSystem) coverageCRS2D);
final GeneralEnvelope bbox = CRS.transform(transform,coverageEnvelope);
bbox.setCoordinateReferenceSystem(coverageCRS2D);
coverageBBox = new ReferencedEnvelope(bbox);
} else {
//it is already a bbox
coverageBBox = new ReferencedEnvelope(coverageEnvelope);
}
}
/**
* Set the main parameters of this coverage request, getting basic
* information from the reader.
*/
private void setBaseParameters() {
this.coverageEnvelope = RasterManager.this.getCoverageEnvelope().clone();
this.coverageRasterArea =(( GridEnvelope2D)RasterManager.this.getCoverageGridrange());
this.coverageCRS = RasterManager.this.getCoverageCRS();
this.coverageGridToWorld2D = (MathTransform2D) RasterManager.this.getRaster2Model();
this.coverageFullResolution = new double[2];
final OverviewLevel highestLevel= RasterManager.this.overviewsController.resolutionsLevels.get(0);
coverageFullResolution[0] = highestLevel.resolutionX;
coverageFullResolution[1] = highestLevel.resolutionY;
}
}
/** Default {@link ColorModel}.*/
ColorModel defaultCM;
/** Default {@link SampleModel}.*/
SampleModel defaultSM;
ImageLayout defaultImageLayout;
/** The CRS of the input coverage */
private CoordinateReferenceSystem coverageCRS;
/** The base envelope related to the input coverage */
private GeneralEnvelope coverageEnvelope;
/** The coverage factory producing a {@link GridCoverage} from an image */
private GridCoverageFactory coverageFactory;
/** The name of the input coverage
* TODO consider URI
*/
private String coverageIdentifier;
private double[] highestRes;
/** The hints to be used to produce this coverage */
private Hints hints;
private URL inputURL;
private int numberOfOvervies;
private double[][] overviewsResolution;
// ////////////////////////////////////////////////////////////////////////
//
// Information obtained by the coverageRequest instance
//
// ////////////////////////////////////////////////////////////////////////
/** The coverage grid to world transformation */
private MathTransform raster2Model;
OverviewsController overviewsController;
private GridEnvelope coverageGridrange;
OverviewPolicy overviewPolicy;
DecimationController decimationController;
ImageMosaicReader parent;
private String locationAttribute;
private PathType pathType;
boolean expandMe;
SpatialDomainManager spatialDomainManager;
/** {@link SoftReference} to the granulesIndex holding the tiles' envelopes. */
private final GranuleCatalog index= new GranuleCatalog();
public RasterManager(final ImageMosaicReader reader) throws DataSourceException {
Utilities.ensureNonNull("ImageMosaicReader", reader);
this.parent=reader;
this.expandMe=parent.expandMe;
inputURL = reader.sourceURL;
locationAttribute=parent.locationAttributeName;
coverageIdentifier=reader.getName();
hints = reader.getHints();
coverageEnvelope = reader.getOriginalEnvelope();
coverageGridrange = reader.getOriginalGridRange();
coverageCRS = reader.getCrs();
raster2Model = reader.getOriginalGridToWorld(PixelInCell.CELL_CENTER);
this.coverageFactory = reader.getGridCoverageFactory();
this.pathType=parent.pathType;
//resolution values
highestRes= reader.getHighestRes();
numberOfOvervies=reader.getNumberOfOvervies();
overviewsResolution=reader.getOverviewsResolution();
//instantiating controller for subsampling and overviews
overviewsController=new OverviewsController();
decimationController= new DecimationController();
try {
spatialDomainManager= new SpatialDomainManager();
} catch (TransformException e) {
throw new DataSourceException(e);
} catch (FactoryException e) {
throw new DataSourceException(e);
}
extractOverviewPolicy();
loadSampleImage();
}
/**
* This code tries to load the sample image from which we can extract SM and CM to use
* when answering to requests that falls within a hole in the mosaic.
*/
private void loadSampleImage() {
final URL baseURL=this.parent.sourceURL;
final File baseFile= DataUtilities.urlToFile(baseURL);
// in case we do not manage to convert the source URL we leave right awaycd sr
if(baseFile==null){
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Unable to find sample image for path "+baseURL);
return;
}
final File sampleImageFile= new File(baseFile.getParent() + "/sample_image");
final RenderedImage sampleImage = ImageMosaicUtils.loadSampleImage(sampleImageFile);
if(sampleImage!=null){
// load SM and CM
defaultCM= sampleImage.getColorModel();
defaultSM= sampleImage.getSampleModel();
// default ImageLayout
defaultImageLayout= new ImageLayout().setColorModel(defaultCM).setSampleModel(defaultSM);
}
else
if(LOGGER.isLoggable(Level.FINE))
LOGGER.warning("Unable to find sample image for path "+baseURL);
}
/**
* This method is responsible for checking the overview policy as defined by
* the provided {@link Hints}.
*
* @return the overview policy which can be one of
* {@link Hints#VALUE_OVERVIEW_POLICY_IGNORE},
* {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST},
* {@link Hints#VALUE_OVERVIEW_POLICY_SPEED}, {@link Hints#VALUE_OVERVIEW_POLICY_QUALITY}.
* Default is {@link Hints#VALUE_OVERVIEW_POLICY_NEAREST}.
*/
private OverviewPolicy extractOverviewPolicy() {
// check if a policy was provided using hints (check even the
// deprecated one)
if (this.hints != null)
if (this.hints.containsKey(Hints.OVERVIEW_POLICY))
overviewPolicy = (OverviewPolicy) this.hints.get(Hints.OVERVIEW_POLICY);
// use default if not provided. Default is nearest
if (overviewPolicy == null)
overviewPolicy = OverviewPolicy.getDefaultPolicy();
assert overviewPolicy != null;
return overviewPolicy;
}
public Collection<GridCoverage2D> read(final GeneralParameterValue[] params) throws IOException
{
// create a request
final RasterLayerRequest request= new RasterLayerRequest(params,this);
if(request.isEmpty()){
if(LOGGER.isLoggable(Level.FINE))
LOGGER.log(Level.FINE,"Request is empty: "+request.toString());
return Collections.emptyList();
}
// create a response for the provided request
final RasterLayerResponse response= new RasterLayerResponse(request,this);
// execute the request
final GridCoverage2D elem=response.createResponse();
if(elem!=null)
return Collections.singletonList(elem);
return Collections.emptyList();
}
public void dispose() {
}
/**
* Retrieves the list of features that intersect the provided envelope
* loading them inside an granulesIndex in memory where needed.
*
* @param envelope
* Envelope for selecting features that intersect.
* @return A list of features.
* @throws IOException
* In case loading the needed features failes.
*/
List<SimpleFeature> getFeaturesFromIndex(final Envelope envelope)throws IOException {
final List<SimpleFeature> features = index.findFeatures(envelope);
if (features != null)
return features;
else
return Collections.emptyList();
}
/**
* Retrieves the list of features that intersect the provided envelope
* loading them inside an granulesIndex in memory where needed.
*
* @param envelope
* Envelope for selecting features that intersect.
* @return A list of features.
* @throws IOException
* In case loading the needed features failes.
*/
void getFeaturesFromIndex(final Envelope envelope,final ItemVisitor visitor)throws IOException {
index.findFeatures(envelope,visitor);
}
public PathType getPathType() {
return pathType;
}
public String getLocationAttribute() {
return locationAttribute;
}
public URL getInputURL() {
return inputURL;
}
public String getCoverageIdentifier() {
return coverageIdentifier;
}
public Hints getHints() {
return hints;
}
public CoordinateReferenceSystem getCoverageCRS() {
return coverageCRS;
}
public GeneralEnvelope getCoverageEnvelope() {
return coverageEnvelope;
}
public GridCoverageFactory getCoverageFactory() {
return coverageFactory;
}
public MathTransform getRaster2Model() {
return raster2Model;
}
public GridEnvelope getCoverageGridrange() {
return coverageGridrange;
}
/**
* Retrieves the footprint for a certain feature ID
* @param id the feature id to get the footprint for
* @return
* the footprint ((polygon or multipolygon) for this granule, or null in case no one is found.
*/
Geometry getGranuleFootprint(final String id) {
return index.getFootprint(id);
}
}