/*
* Geotoolkit - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2011-2014, Geomatys
*
* 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.geotoolkit.storage.coverage;
import java.awt.Dimension;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.RenderedImage;
import java.awt.image.SampleModel;
import java.awt.image.WritableRaster;
import java.io.IOException;
import java.util.*;
import java.util.Map.Entry;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CancellationException;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageReader;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.logging.Logging;
import org.geotoolkit.coverage.CoverageStack;
import org.geotoolkit.coverage.GridCoverageStack;
import org.geotoolkit.coverage.GridSampleDimension;
import org.geotoolkit.coverage.finder.CoverageFinder;
import org.geotoolkit.coverage.finder.DefaultCoverageFinder;
import org.geotoolkit.coverage.grid.*;
import org.geotoolkit.coverage.io.CoverageStoreException;
import org.geotoolkit.coverage.io.GridCoverageReadParam;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.factory.FactoryFinder;
import org.geotoolkit.image.io.XImageIO;
import org.geotoolkit.util.NamesExt;
import org.geotoolkit.image.iterator.PixelIterator;
import org.geotoolkit.image.iterator.PixelIteratorFactory;
import org.geotoolkit.internal.referencing.CRSUtilities;
import org.geotoolkit.referencing.ReferencingUtilities;
import org.geotoolkit.util.Cancellable;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.Envelope;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.cs.CoordinateSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.TransformException;
import org.opengis.util.FactoryException;
import org.opengis.util.GenericName;
import org.opengis.util.NameFactory;
import org.opengis.util.NameSpace;
import org.apache.sis.util.Utilities;
/**
* GridCoverage reader on top of a Pyramidal object.
*
* @author Johann Sorel (Geomatys)
* @module
*/
public class PyramidalModelReader extends GridCoverageReader{
private CoverageReference ref;
private final CoverageFinder coverageFinder;
protected static final Logger LOGGER = Logging.getLogger("org.geotoolkit.storage.coverage");
@Deprecated
public PyramidalModelReader() {
this.coverageFinder = new DefaultCoverageFinder();
}
public PyramidalModelReader(CoverageFinder coverageFinder) {
this.coverageFinder = coverageFinder;
}
@Override
public CoverageReference getInput() {
return ref;
}
private PyramidalCoverageReference getPyramidalModel(){
return (PyramidalCoverageReference)ref;
}
@Override
public void setInput(Object input) throws CoverageStoreException {
if(!(input instanceof CoverageReference) || !(input instanceof PyramidalCoverageReference)){
throw new CoverageStoreException("Unsupported input type, can only be CoverageReference implementing PyramidalModel.");
}
this.ref = (CoverageReference) input;
super.setInput(input);
}
@Override
public List<? extends GenericName> getCoverageNames() throws CoverageStoreException, CancellationException {
final NameFactory dnf = FactoryFinder.getNameFactory(null);
final String nss = NamesExt.getNamespace(getInput().getName());
final String nameSpace = nss != null ? nss : "http://geotoolkit.org" ;
final NameSpace ns = dnf.createNameSpace(dnf.createGenericName(null, nameSpace), null);
final GenericName gn = dnf.createLocalName(ns, getInput().getName().tip().toString());
return Collections.singletonList(gn);
}
@Override
public GeneralGridGeometry getGridGeometry(int index) throws CoverageStoreException, CancellationException {
final PyramidSet set;
try {
set = getPyramidalModel().getPyramidSet();
} catch (DataStoreException ex) {
throw new CoverageStoreException(ex);
}
final GeneralGridGeometry gridGeom;
if (!set.getPyramids().isEmpty()) {
//-- we use the first pyramid as default
final Pyramid pyramid = set.getPyramids().iterator().next();
final List<GridMosaic> mosaics = new ArrayList<>(pyramid.getMosaics());
Collections.sort(mosaics, CoverageFinder.SCALE_COMPARATOR);
if (mosaics.isEmpty()) {
//no mosaics
gridGeom = new GeneralGridGeometry(null, null, set.getEnvelope());
} else {
final CoordinateReferenceSystem crs = pyramid.getCoordinateReferenceSystem();
final CoordinateSystem cs = crs.getCoordinateSystem();
final int nbdim = cs.getDimension();
//-- use the first mosaic informations, most accurate
final GridMosaic mosaic = mosaics.get(0);
final Dimension gridSize = mosaic.getGridSize();
final Dimension tileSize = mosaic.getTileSize();
//-- we expect no rotation
final MathTransform gridToCRS = AbstractGridMosaic.getTileGridToCRS2D(mosaic, new Point(0, 0));
//-- get all mosaics with same scale
final double scal = mosaic.getScale();
for (int m = mosaics.size() - 1; m >= 0; m--) {
if (mosaics.get(m).getScale() != scal) mosaics.remove(m);
}
final int minordi = CRSUtilities.firstHorizontalAxis(crs);
final Map<Integer , double[]> multiAxisValues = new HashMap<>();
for (int i = 0; i < cs.getDimension(); i++) {
if (i != minordi && i!= minordi + 1) {
//-- pass by TreeSet to avoid duplicate
final SortedSet<Double> axisValues = new TreeSet();
for (final GridMosaic gridMos : mosaics) {
axisValues.add(gridMos.getUpperLeftCorner().getOrdinate(i));
}
//-- convert Double[] -> double[]
final double[] axVals = new double[axisValues.size()];
int a = 0;
for (Double d : axisValues) axVals[a++] = d;
Arrays.sort(axVals, 0, axVals.length);
multiAxisValues.put(i, axVals);
}
}
final MathTransform gridToCRSds = ReferencingUtilities.toTransform(minordi, gridToCRS, multiAxisValues, cs.getDimension());
final int[] low = new int[nbdim];
final int[] high = new int[nbdim];
for (int i = 0; i < cs.getDimension(); i++) {
low[i] = 0; //-- on each dimension low begin at 0
if (i == minordi) {
high[i] = gridSize.width * tileSize.width; //-- X horizontal 2D part
} else if (i == minordi + 1) {
high[i] = gridSize.height * tileSize.height; //-- Y horizontal 2D part
} else if (i != minordi && i != minordi + 1) {
high[i] = multiAxisValues.get(i).length; //-- other dimension grid high value = discret axis values number.
} else {
//-- should never append
throw new IllegalStateException("PyramidalModelReader.getGridGeometry() : problem during grid creation.");
}
}
final GeneralGridEnvelope ge = new GeneralGridEnvelope(low, high, false);
gridGeom = new GeneralGridGeometry(ge, PixelInCell.CELL_CORNER, gridToCRSds, crs);
}
} else {
//-- empty pyramid set
gridGeom = new GeneralGridGeometry(null, null, set.getEnvelope());
}
return gridGeom;
}
@Override
public List<GridSampleDimension> getSampleDimensions(int index) throws CoverageStoreException, CancellationException {
try {
return getPyramidalModel().getSampleDimensions();
} catch (DataStoreException ex) {
throw new CoverageStoreException(ex.getMessage(), ex);
}
}
@Override
public GridCoverage read(int index, GridCoverageReadParam param) throws CoverageStoreException, CancellationException {
if (index != 0)
throw new CoverageStoreException("Invalid Image index.");
if (param == null)
param = new GridCoverageReadParam();
final int[] desBands = param.getDestinationBands();
final int[] sourceBands = param.getSourceBands();
if (desBands != null || sourceBands != null)
throw new CoverageStoreException("Source or destination bands can not be used on pyramidal coverages.");
CoordinateReferenceSystem crs = param.getCoordinateReferenceSystem();
Envelope paramEnv = param.getEnvelope();
double[] resolution = param.getResolution();
// Build proper envelope and CRS from parameters. If null, they're set from the queried coverage information.
if (paramEnv == null)
paramEnv = getGridGeometry(index).getEnvelope();
if (crs == null) {
crs = paramEnv.getCoordinateReferenceSystem();
} else {
try {
paramEnv = Envelopes.transform(paramEnv, crs);
} catch (TransformException ex) {
throw new CoverageStoreException("Could not transform coverage envelope to given crs.", ex);
}
}
if (crs == null)
throw new CoverageStoreException("CRS not defined in parameters or input envelope.");
//-- estimate resolution if not given
if (resolution == null)
//-- set resolution to infinite, will select the last mosaic level
resolution = new double[]{Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY};
final PyramidalCoverageReference covref = getPyramidalModel();
final PyramidSet pyramidSet;
try {
pyramidSet = covref.getPyramidSet();
} catch (DataStoreException ex) {
throw new CoverageStoreException(ex);
}
Pyramid pyramid;
try {
pyramid = coverageFinder.findPyramid(pyramidSet, crs);
} catch (FactoryException ex) {
throw new CoverageStoreException(ex);
}
//-- no reliable pyramid
if (pyramid == null)
throw new CoverageStoreException("No pyramid defined.");
/*
* We will transform the input envelope to found pyramid CRS.
*/
final CoordinateReferenceSystem pyramidCRS = pyramid.getCoordinateReferenceSystem();
GeneralEnvelope wantedEnv;
try {
wantedEnv = new GeneralEnvelope(ReferencingUtilities.transform(paramEnv, pyramidCRS));
} catch (TransformException ex) {
throw new CoverageStoreException(ex.getMessage(), ex);
}
//the wanted image resolution
double wantedResolution = resolution[0];
final double tolerance = 0.1d;
//-- transform resolution into pyramid crs
if (!(crs.equals(pyramidCRS))) {
final int displayBoundX = (int) ((paramEnv.getSpan(0) + wantedResolution - 1) / wantedResolution); //-- to force minimum mosaic resolution
wantedResolution = wantedEnv.getSpan(0) / displayBoundX;
}
//----------------------------------------
List<GridMosaic> mosaics;
try {
mosaics = coverageFinder.findMosaics(pyramid, wantedResolution, tolerance, wantedEnv);
} catch(MismatchedDimensionException ex) {
throw new CoverageStoreException(ex.getMessage(),ex);
}
if (mosaics.isEmpty())
throw new IllegalStateException("Unexpected comportement an error should be precedently occurs.");
//-- we definitely do not want some NaN values
for (int i = 0 ; i < wantedEnv.getDimension(); i++) {
if (Double.isNaN(wantedEnv.getMinimum(i)))
wantedEnv.setRange(i, Double.NEGATIVE_INFINITY, wantedEnv.getMaximum(i));
if (Double.isNaN(wantedEnv.getMaximum(i)))
wantedEnv.setRange(i, wantedEnv.getMinimum(i), Double.POSITIVE_INFINITY);
}
//-- read the data
final boolean deferred = param.isDeferred();
try {
if (mosaics.size() == 1) {
//-- read a single slice
return readSlice(mosaics.get(0), wantedEnv, deferred);
} else {
//-- read a data cube of multiple slices
return readCube(mosaics, wantedEnv, deferred);
}
} catch (DataStoreException ex) {
throw new CoverageStoreException(ex); //-- to be in accordance with reader method interface signature
}
}
/**
* Build a coverage from a Grid mosaic definition.
*
* @param mosaic original data grid mosaic
* @param wantedEnv area to read : must be in mosaic CRS, of a subset of it
* @param deferred true to delay tile reading, set to true to use a LargeRenderedImage
* @return GridCoverage
* @throws CoverageStoreException
*/
private GridCoverage readSlice(GridMosaic mosaic, Envelope wantedEnv, boolean deferred) throws CoverageStoreException, DataStoreException {
final CoordinateReferenceSystem wantedCRS = wantedEnv.getCoordinateReferenceSystem();
final Envelope mosEnvelope = mosaic.getEnvelope();
//-- check CRS conformity
if (!(Utilities.equalsIgnoreMetadata(wantedCRS, mosEnvelope.getCoordinateReferenceSystem())))
throw new IllegalArgumentException("the wantedEnvelope is not define in same CRS than mosaic. Expected : "
+mosEnvelope.getCoordinateReferenceSystem()+". Found : "+wantedCRS);
final DirectPosition upperLeft = mosaic.getUpperLeftCorner();
assert Utilities.equalsIgnoreMetadata(upperLeft.getCoordinateReferenceSystem(), wantedCRS);
final ViewType currentViewType = getPyramidalModel().getPackMode();
final int xAxis = CRSUtilities.firstHorizontalAxis(wantedCRS);
final int yAxis = xAxis +1;
//-- convert working into 2D space
final CoordinateReferenceSystem mosCRS2D;
final GeneralEnvelope wantedEnv2D, mosEnv2D;
try {
mosCRS2D = CRSUtilities.getCRS2D(wantedCRS);
wantedEnv2D = GeneralEnvelope.castOrCopy(Envelopes.transform(wantedEnv, mosCRS2D));
mosEnv2D = GeneralEnvelope.castOrCopy(Envelopes.transform(mosEnvelope, mosCRS2D));
} catch(Exception ex) {
throw new CoverageStoreException(ex);
}
//-- define appropriate gridToCRS
final Dimension gridSize = mosaic.getGridSize();
final Dimension tileSize = mosaic.getTileSize();
final double sx = mosEnv2D.getSpan(0) / (gridSize.width * tileSize.width);
final double sy = mosEnv2D.getSpan(1) / (gridSize.height * tileSize.height);
final double offsetX = upperLeft.getOrdinate(xAxis);
final double offsetY = upperLeft.getOrdinate(yAxis);
final AffineTransform2D gridToCrs2D = new AffineTransform2D(sx, 0, 0, -sy, offsetX, offsetY);
final GeneralEnvelope envelopOfInterest2D = new GeneralEnvelope(wantedEnv2D);
envelopOfInterest2D.intersect(mosEnv2D);
final Envelope gridOfInterest;
try {
gridOfInterest = Envelopes.transform(gridToCrs2D.inverse(), envelopOfInterest2D);
} catch (Exception ex) {
throw new CoverageStoreException(ex);
}
final long bBoxMinX = StrictMath.round(gridOfInterest.getMinimum(0));
final long bBoxMaxX = StrictMath.round(gridOfInterest.getMaximum(0));
final long bBoxMinY = StrictMath.round(gridOfInterest.getMinimum(1));
final long bBoxMaxY = StrictMath.round(gridOfInterest.getMaximum(1));
final int tileMinCol = (int) (bBoxMinX / tileSize.width);
final int tileMaxCol = (int) StrictMath.ceil(bBoxMaxX / (double) tileSize.width);
assert tileMaxCol == ((int)((bBoxMaxX + tileSize.width - 1) / (double) tileSize.width)) : "readSlice() : unexpected comportement maximum column index.";
final int tileMinRow = (int) (bBoxMinY / tileSize.height);
final int tileMaxRow = (int) StrictMath.ceil(bBoxMaxY / (double) tileSize.height);
assert tileMaxRow == ((int)((bBoxMaxY + tileSize.height - 1) / (double) tileSize.height)) : "readSlice() : unexpected comportement maximum row index.";
//-- debug helper
{
// System.out.println("index X mosaic : 0 -> "+gridSize.width);
// System.out.println("index Y mosaic : 0 -> "+gridSize.height);
// System.out.println("mosaic grid = (0, 0) --> ("+(gridSize.width*tileSize.width)+", "+(gridSize.height * tileSize.height)+")");
//
// System.out.println("requested index X : "+tileMinCol+" -> "+tileMaxCol);
// System.out.println("requested index Y : "+tileMinRow+" -> "+tileMaxRow);
// System.out.println("gridOfInterest = "+gridOfInterest.toString());
}
RenderedImage image = null;
if (deferred) {
//delay reading tiles
image = new GridMosaicRenderedImage(mosaic, new Rectangle(
(int)tileMinCol, (int)tileMinRow, (int)(tileMaxCol-tileMinCol), (int)(tileMaxRow-tileMinRow)));
} else {
//tiles to render, coordinate in grid -> image offset
final Collection<Point> candidates = new ArrayList<>();
for (int tileCol = (int) tileMinCol; tileCol < tileMaxCol; tileCol++) {
for(int tileRow = (int) tileMinRow; tileRow < tileMaxRow; tileRow++) {
if (mosaic.isMissing(tileCol, tileRow)) continue;//--tile not available
candidates.add(new Point(tileCol, tileRow));
}
}
if (candidates.isEmpty()) {
//no tiles intersect
LOGGER.log(Level.FINE, "Following Requested envelope : "
+wantedEnv
+ "\n do not intersect data define by following data Envelope."
+mosaic.getEnvelope());
return null;
}
//--debug helper
{
// System.out.println("retained mosaics : ");
// for (Point candidate : candidates) {
// System.out.println("mosaic : ("+candidate.x+", "+candidate.y+")");
// }
}
//aggregation ----------------------------------------------------------
final Map hints = Collections.EMPTY_MAP;
final BlockingQueue<Object> queue;
try {
queue = mosaic.getTiles(candidates, hints);
} catch (DataStoreException ex) {
throw new CoverageStoreException(ex.getMessage(),ex);
}
int i = 0;
while(true){
Object obj = null;
try {
obj = queue.poll(100, TimeUnit.MILLISECONDS);
} catch (InterruptedException ex) {
//not important
}
if(abortRequested){
if(queue instanceof Cancellable){
((Cancellable)queue).cancel();
}
break;
}
if(obj == GridMosaic.END_OF_QUEUE){
break;
}
if(obj instanceof TileReference){
final TileReference tile = (TileReference)obj;
final Point position = tile.getPosition();
final Point offset = new Point(
(int)(position.x-tileMinCol)*tileSize.width,
(int)(position.y-tileMinRow)*tileSize.height);
final Object input = tile.getInput();
RenderedImage tileImage = null;
if(input instanceof RenderedImage){
tileImage = (RenderedImage) input;
}else{
ImageReader reader = null;
try {
reader = tile.getImageReader();
tileImage = reader.read(tile.getImageIndex());
} catch (IOException ex) {
throw new CoverageStoreException(ex.getMessage(),ex);
} finally {
XImageIO.disposeSilently(reader);
}
}
//-- if photographic transform ARGB
if (ViewType.PHOTOGRAPHIC.equals(currentViewType)) {
//-- transform argb
tileImage = forceAlpha(tileImage);
}
if (image == null) {
ColorModel cm = null;
SampleModel sm = null;
if (ref instanceof PyramidalCoverageReference) {
final PyramidalCoverageReference pyramRef = (PyramidalCoverageReference) ref;
cm = pyramRef.getColorModel();
sm = pyramRef.getSampleModel();
}
if(cm==null) {
cm = tileImage.getColorModel();
}
if(sm==null){
//if sample model is null, we need to have a coherent relation with
//the color model. we reuse the tile models.
cm = tileImage.getColorModel();
sm = tileImage.getSampleModel();
}
sm = sm.createCompatibleSampleModel((int)(tileMaxCol-tileMinCol)*tileSize.width,
(int)(tileMaxRow-tileMinRow)*tileSize.height);
final WritableRaster raster = WritableRaster.createWritableRaster(sm, null);
image = new BufferedImage(cm,raster,
cm.isAlphaPremultiplied(), new Hashtable<>());
}
//-- write current read tile into destination image.
final Rectangle tileBound = new Rectangle(offset.x, offset.y, tileImage.getWidth(), tileImage.getHeight());
final PixelIterator destPix = PixelIteratorFactory.createDefaultWriteableIterator((BufferedImage)image, (BufferedImage)image, tileBound);
final PixelIterator tilePix = PixelIteratorFactory.createDefaultIterator(tileImage);
while(destPix.next()) {
tilePix.next();
destPix.setSampleDouble(tilePix.getSampleDouble());
}
assert !tilePix.next();
}
}
if(image == null){
image = new BufferedImage(
(int)(tileMaxCol-tileMinCol)*tileSize.width,
(int)(tileMaxRow-tileMinRow)*tileSize.height,
BufferedImage.TYPE_INT_ARGB);
}
}
////
//// //-- if DatabufferType of image is float or Double we must change the Color Space
//// //-- to bound sample value between 0 and 1 to avoid java 2d rendering problem
//// image = ImageUtils.replaceFloatingColorModel(image);
//build the coverage ---------------------------------------------------
final GridCoverageBuilder gcb = new GridCoverageBuilder();
gcb.setName(ref.getName().tip().toString());
final List<GridSampleDimension> dimensions = getSampleDimensions(ref.getImageIndex());
if (dimensions != null) {
gcb.setSampleDimensions(dimensions.toArray(new GridSampleDimension[dimensions.size()]));
}
final GridEnvelope ge = new GeneralGridEnvelope(image, wantedCRS.getCoordinateSystem().getDimension());
final MathTransform gtc = AbstractGridMosaic.getTileGridToCRSND(mosaic,
new Point((int)tileMinCol,(int)tileMinRow),wantedCRS.getCoordinateSystem().getDimension());
final GridGeometry2D gridgeo = new GridGeometry2D(ge, PixelOrientation.UPPER_LEFT, gtc, wantedCRS, null);
gcb.setGridGeometry(gridgeo);
gcb.setRenderedImage(image);
return gcb.build();
}
/**
* Add an alpha band to the image and remove any black border if asked.
*
* TODO, this could be done more efficiently by adding an ImageLayout hints
* when doing the coverage reprojection. but hints can not be passed currently.
*/
private static RenderedImage forceAlpha(RenderedImage img) {
if (!img.getColorModel().hasAlpha()) {
//Add alpha channel
final BufferedImage buffer = new BufferedImage(img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB);
buffer.createGraphics().drawRenderedImage(img, new AffineTransform());
img = buffer;
}
return img;
}
private GridCoverage readCube(List<GridMosaic> mosaics, Envelope wantedEnv, boolean deferred) throws CoverageStoreException, DataStoreException{
//regroup mosaic by hierarchy cubes
final TreeMap groups = new TreeMap();
for(GridMosaic mosaic : mosaics){
appendSlice(groups, mosaic);
}
int dim = wantedEnv.getDimension();
//rebuild coverage
return rebuildCoverage(groups, wantedEnv, deferred, dim-1);
}
/**
* Organize mosaics in groups which are on the same dimension slice.
*
* @param rootGroup
* @param mosaic
* @throws CoverageStoreException
*/
private void appendSlice(final TreeMap<Double,Object> rootGroup, GridMosaic mosaic) throws CoverageStoreException{
final DirectPosition upperLeft = mosaic.getUpperLeftCorner();
TreeMap<Double,Object> groups = rootGroup;
//regroup them by inverse axis order so we can rebuild stacks always adding dimensions at the end
for(int i=upperLeft.getDimension()-1; i>=2; i--){
final double d = upperLeft.getOrdinate(i);
final Object obj = groups.get(d);
if(obj==null){
groups.put(d, mosaic);
break;
}else if(obj instanceof GridMosaic){
//already another mosaic for the dimension slice
//replace the coverage by a map and re-add them.
groups.put(d, new TreeMap());
appendSlice(rootGroup, (GridMosaic)obj);
appendSlice(rootGroup, mosaic);
break;
}else if(obj instanceof TreeMap){
groups = (TreeMap) obj;
}else{
throw new CoverageStoreException("Found an object which is not a Coverage or a Map group, should not happen : "+obj);
}
}
}
/**
*
* @param groups
* @param wantedEnv
* @param deferred
* @param axisIndex
* @return GridCoverage
* @throws CoverageStoreException
*/
private GridCoverage rebuildCoverage(TreeMap<Double,Object> groups, Envelope wantedEnv, boolean deferred, int axisIndex)
throws CoverageStoreException, DataStoreException {
final CoordinateReferenceSystem crs = wantedEnv.getCoordinateReferenceSystem();
final CoordinateSystem cs = crs.getCoordinateSystem();
int nbDim = cs.getDimension();
final List<CoverageStack.Element> elements = new ArrayList<>();
final List<Entry<Double,Object>> entries = new ArrayList<>(groups.entrySet());
for(int k=0,kn=entries.size();k<kn;k++){
final Entry<Double,Object> entry = entries.get(k);
final Double z = entry.getKey();
final Object obj = entry.getValue();
final GeneralEnvelope sliceEnvelop = new GeneralEnvelope(crs);
for (int i = 0; i < nbDim; i++) {
if (i == axisIndex) {
sliceEnvelop.setRange(i, z, z);
} else {
sliceEnvelop.setRange(i, wantedEnv.getMinimum(i), wantedEnv.getMaximum(i));
}
}
final GridCoverage subCoverage;
if(obj instanceof GridMosaic){
subCoverage = readSlice((GridMosaic)obj, sliceEnvelop, deferred);
}else if(obj instanceof TreeMap){
subCoverage = rebuildCoverage((TreeMap)obj, sliceEnvelop, deferred, axisIndex-1);
}else{
throw new CoverageStoreException("Found an object which is not a Coverage or a Map group, should not happen : "+obj);
}
//calculate the range
double min;
double max;
if(k==0){
if(kn==1){
//a single element, use a range of 1
min = z - 0.5;
max = z + 0.5;
}else{
final double nextD = entries.get(k+1).getKey();
final double diff = (nextD - z) / 2.0;
min = z-diff;
max = z+diff;
}
}else if(k==kn-1){
final double previousD = entries.get(k-1).getKey();
final double diff = (z - previousD) / 2.0;
min = z-diff;
max = z+diff;
}else{
final double prevD = entries.get(k-1).getKey();
final double nextD = entries.get(k+1).getKey();
min = z - (z - prevD) / 2.0;
max = z + (nextD - z) / 2.0;
}
elements.add(new CoverageStack.Adapter(subCoverage, NumberRange.create(min, true, max, false), z));
}
try {
return new GridCoverageStack("HyperCube"+ nbDim +"D", crs, elements, axisIndex);
} catch (IOException ex) {
throw new CoverageStoreException(ex);
} catch (TransformException ex) {
throw new CoverageStoreException(ex);
} catch (FactoryException ex) {
throw new CoverageStoreException(ex);
}
}
}