/*
* Geotoolkit.org - An Open Source Java GIS Toolkit
* http://www.geotoolkit.org
*
* (C) 2012, 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.*;
import java.awt.image.*;
import java.io.IOException;
import java.util.*;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import javax.imageio.ImageReader;
import javax.swing.ProgressMonitor;
import org.apache.sis.geometry.Envelopes;
import org.apache.sis.geometry.GeneralDirectPosition;
import org.apache.sis.geometry.GeneralEnvelope;
import org.apache.sis.measure.NumberRange;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.util.ArgumentChecks;
import org.geotoolkit.coverage.GridSampleDimension;
import org.geotoolkit.coverage.grid.GridCoverage2D;
import org.geotoolkit.coverage.grid.GridGeometry2D;
import org.geotoolkit.coverage.io.GridCoverageReadParam;
import org.geotoolkit.coverage.io.GridCoverageReader;
import org.geotoolkit.image.io.XImageIO;
import org.opengis.util.GenericName;
import org.geotoolkit.image.interpolation.InterpolationCase;
import org.geotoolkit.image.interpolation.LanczosInterpolation;
import org.geotoolkit.image.interpolation.Resample;
import org.geotoolkit.image.interpolation.ResampleBorderComportement;
import org.geotoolkit.internal.referencing.CRSUtilities;
import org.geotoolkit.process.ProcessDescriptor;
import org.geotoolkit.process.ProcessEvent;
import org.geotoolkit.process.ProcessException;
import org.geotoolkit.process.ProcessListener;
import org.apache.sis.referencing.CRS;
import org.apache.sis.referencing.operation.transform.MathTransforms;
import org.apache.sis.internal.referencing.j2d.AffineTransform2D;
import org.geotoolkit.coverage.grid.GeneralGridGeometry;
import org.geotoolkit.coverage.combineIterator.GridCombineIterator;
import org.geotoolkit.referencing.ReferencingUtilities;
import org.geotoolkit.image.BufferedImages;
import org.geotoolkit.image.internal.ImageUtilities;
import org.geotoolkit.image.io.large.AbstractLargeRenderedImage;
import org.opengis.coverage.SampleDimension;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridGeometry;
import org.opengis.geometry.DirectPosition;
import org.opengis.geometry.Envelope;
import org.opengis.metadata.lineage.ProcessStep;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.parameter.ParameterValueGroup;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.*;
import org.opengis.util.FactoryException;
import org.apache.sis.util.Utilities;
/**
* <p>Resampling, re-project, tile cut and insert in given datastore, image from
* {@link GridCoverage} or {@link GridCoverageReader}.<br/><br/>
*
* Use example : <br/><br/>
* We own a {@link GridCoverage} or {@link GridCoverageReader} and a {@link CoverageStore}.<br/>
* {@code final GridCoverage myGridCoverage;}<br/>
* {@code final CoverageStore myCoverageStore;}<br/><br/>
* We want project our {@link GridCoverage} in 2 others {@link CoordinateReferenceSystem}
* and insert results in our database with differents scale levels.<br/><br/>
*
* In first time we build {@link PGCoverageBuilder} object and define interpolation properties use during resampling.<br/>
*
* {@code final PGCoverageBuilder pgcb = new PGCoverageBuilder(new Dimension(200, 200), InterpolationCase.BICUBIC, 2);}.<br/><br/>
* See {@link PGCoverageBuilder#PGCoverageBuilder(java.awt.Dimension, org.geotoolkit.image.interpolation.InterpolationCase, int) }<br/><br/>
*
* We choose {@link CoordinateReferenceSystem}.<br/>
* {@code final CoordinateReferenceSystem crs4326 = CRS.forCode("EPSG:4326");}<br/>
* {@code final CoordinateReferenceSystem crs2163 = CRS.forCode("EPSG:2163");}<br/>
* See <a href="http://www.geotoolkit.org/modules/referencing/supported-codes.html">List of authority codes</a>
* for more {@link CoordinateReferenceSystem}.<br/><br/>
*
* We define work area with appropriate {@link Envelope}.<br/>
* {@code final GeneralEnvelope envCRS4326 = new GeneralEnvelope(crs4326);}<br/>
* {@code envCRS4326.setEnvelope(xmin, ymin,}
{@code , xmax, ymax);}<br/>
* {@code final GeneralEnvelope envCRS2163 = new GeneralEnvelope(crs2163);}<br/>
* {@code envCRS2163.setEnvelope(xmin, ymin,}
{@code , xmax, ymax);}<br/>
* See {@link CoordinateReferenceSystem#getDomainOfValidity()} for appropriate ordinates values.<br/><br/>
*
* We define appropriate scale level from envelope size.<br/>
* {@code final double[] scaleLvlCRS4329 = new double[]{val0, val1, ... , valn};}<br/>
* {@code final double[] scaleLvlCRS2163 = new double[]{val0, val1, ... , valn};}<br/>
* Note : output stored image size : <br/>
* output image width = {@link Envelope#getSpan(0) } / {val0,
, valn}.<br/>
* output image height = {@link Envelope#getSpan(1) } / {val0,
, valn}.<br/><br/>
*
* We associate work area ({@link Envelope}) with theirs scale levels.<br/>
* {@code final HashMap<Envelope, double[]> resolution_Per_Envelope = new HashMap<Envelope, double[]>();}<br/>
* {@code resolution_Per_Envelope.put(envCRS4326, scaleLvlCRS4329);}<br/>
* {@code resolution_Per_Envelope.put(envCRS2163, scaleLvlCRS2163);}<br/><br/>
*
* We define pyramid name.<br/>
* {@code final Name name = new DefaultName("myName");}<br/><br/>
* and table in case when pixel transformation is out of source image boundary.<br/>
* {@code final double[] fillValue = new double[]{1, 2, 3}};//for example.<br/><br/>
*
* In last time, call create method.<br/>
* {@code pgcb.create(myGridCoverage, myCoverageStore, name, resolution_Per_Envelope, fillValue);}</p>
*
* @author RĂ©mi Marechal (Geomatys).
* @author Quentin Boileau (Geomatys).
* @author Johann Sorel (Geomatys).
*/
public class PyramidCoverageBuilder {
/**
* The default tile size in pixels.
*/
private static final int DEFAULT_TILE_SIZE = 256;
/**
* Minimum tile size.
*/
private static final int MIN_TILE_SIZE = 64;
/**
* Tile width.
*/
private final int tileWidth;
/**
* Tile height.
*/
private final int tileHeight;
/**
* Interpolation properties.
*/
private final InterpolationCase interpolationCase;
private final int lanczosWindow;
/**
* Flag to re-use mosaic tiles if already exist in output pyramid.
* Set at false by default.
*/
private boolean reuseTiles = false;
/**
* Global number of tiles which will be generate.
* @see PyramidCoverageBuilder#initListener(java.util.Map, org.geotoolkit.process.ProcessListener)
*/
private int globalTileNumber;
/**
* The current nth tile.
*/
private int niemeTile;
/**
* Used for events.
*/
private final org.geotoolkit.process.Process fakeProcess = new org.geotoolkit.process.Process() {
@Override
public ProcessDescriptor getDescriptor() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public ParameterValueGroup getInput() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public ParameterValueGroup call() throws ProcessException {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public ProcessStep getMetadata() {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void addListener(ProcessListener listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public void removeListener(ProcessListener listener) {
throw new UnsupportedOperationException("Not supported yet.");
}
@Override
public ProcessListener[] getListeners() {
throw new UnsupportedOperationException("Not supported yet.");
}
};
/**
* <p>Define tile size and interpolation properties use during resampling operation.<br/><br/>
*
* Note : if lanczos interpolation doesn't choose lanczosWindow parameter has no impact.</p>
*
* @param tileSize size of tile from mosaic if null a default tile size of 256 x 256 is chosen. Minimum tile size is 64 x 64.
* @param interpolation pixel operation use during resampling operation.
* @param lanczosWindow only use about Lanczos interpolation.
* @see Resample#fillImage()
* @see LanczosInterpolation#LanczosInterpolation(org.geotoolkit.image.iterator.PixelIterator, int)
* @see InterpolationCase
*/
public PyramidCoverageBuilder(Dimension tileSize, InterpolationCase interpolation, int lanczosWindow) {
this(tileSize, interpolation, lanczosWindow, false);
}
/**
* <p>Define tile size and interpolation properties use during resampling operation.<br/><br/>
*
* Note : if lanczos interpolation doesn't choose lanczosWindow parameter has no impact.</p>
*
* @param tileSize size of tile from mosaic if null a default tile size of 256 x 256 is chosen. Minimum tile size is 64 x 64.
* @param interpolation pixel operation use during resampling operation.
* @param lanczosWindow only use about Lanczos interpolation.
* @param reuseTiles flag to re-use mosaic tiles if already exist in output pyramid.
* @see Resample#fillImage()
* @see LanczosInterpolation#LanczosInterpolation(org.geotoolkit.image.iterator.PixelIterator, int)
* @see InterpolationCase
*/
public PyramidCoverageBuilder(Dimension tileSize, InterpolationCase interpolation, int lanczosWindow, boolean reuseTiles) {
ArgumentChecks.ensureNonNull("interpolation", interpolation);
ArgumentChecks.ensureStrictlyPositive("lanczosWindow", lanczosWindow);
if (tileSize == null) {
tileWidth = tileHeight = DEFAULT_TILE_SIZE;
} else {
tileWidth = Math.min(DEFAULT_TILE_SIZE, Math.max(tileSize.width, MIN_TILE_SIZE));
tileHeight = Math.min(DEFAULT_TILE_SIZE, Math.max(tileSize.height, MIN_TILE_SIZE));
}
this.interpolationCase = interpolation;
this.lanczosWindow = lanczosWindow;
this.reuseTiles = reuseTiles;
}
/**
* <p>Effectuate resampling, re-projection, tile cutting and insertion in datastore on {@link GridCoverage}.<br/><br/>
*
* Note : <br/>
* {@link GridGeometry} from {@link GridCoverage} must be instance of {@link GridGeometry2D}
* else a {@link IllegalArgumentException} will be thrown.<br/>
* fillValue parameter must have same lenght than pixel size from image within coverage.<br/>
* If fill value is {@code null} a table of zero value with appropriate lenght is use.
* </p>
* @param gridCoverage {@link GridCoverage} which will be stored.
* @param coverageStore {@link CoverageStore} where operation on {@link GridCoverage} are stored.
* @param coverageName name given to the set of operations results, performed on the coverage in the datastore.
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @throws DataStoreException if tile writing throw exception.
* @throws TransformException if problems during resampling operation.
* @throws FactoryException if impossible to find {@code MathTransform} between two {@link CoordinateReferenceSystem}.
*/
public void create(GridCoverage gridCoverage, CoverageStore coverageStore, GenericName coverageName,
Map<Envelope, double[]> resolution_Per_Envelope, double[] fillValue)
throws DataStoreException, TransformException, FactoryException, IOException {
create(gridCoverage, coverageStore, coverageName, resolution_Per_Envelope, fillValue, null, null);
}
/**
* <p>Effectuate resampling, re-projection, tile cutting and insertion in datastore on {@link GridCoverage}.<br/><br/>
*
* Note : <br/>
* {@link GridGeometry} from {@link GridCoverage} must be instance of {@link GridGeometry2D}
* else a {@link IllegalArgumentException} will be thrown.<br/>
* fillValue parameter must have same lenght than pixel size from image within coverage.<br/>
* If fill value is {@code null} a table of zero value with appropriate lenght is use.
* </p>
* @param gridCoverageRef {@link GridCoverage} which will be stored.
* @param coverageStore {@link CoverageStore} where operation on {@link GridCoverage} are stored.
* @param coverageName name given to the set of operations results, performed on the coverage in the datastore.
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @param processListener {@link ProcessListener} to send state informations (should be null).
* @param monitor A progress monitor used for detecting a cancel request during the process. Can be {@code null}.
* @throws DataStoreException if tile writing throw exception.
* @throws TransformException if problems during resampling operation.
* @throws FactoryException if impossible to find {@code MathTransform} between two {@link CoordinateReferenceSystem}.
*/
public void create(CoverageReference gridCoverageRef, CoverageStore coverageStore, GenericName coverageName,
Map<Envelope, double[]> resolution_Per_Envelope, double[] fillValue,
ProcessListener processListener, ProgressMonitor monitor)
throws DataStoreException, TransformException, FactoryException, IOException {
ArgumentChecks.ensureNonNull("CoverageReference" , gridCoverageRef);
ArgumentChecks.ensureNonNull("output CoverageStore", coverageStore);
ArgumentChecks.ensureNonNull("coverageName" , coverageName);
ArgumentChecks.ensureNonNull("resolution_Per_Envelope", resolution_Per_Envelope);
//one coverageReference for each reader.
final CoverageReference cv = getOrCreateCRef(coverageStore,coverageName);
if (!(cv instanceof PyramidalCoverageReference)) {
final IllegalArgumentException ex = new IllegalArgumentException("CoverageStore parameter should be instance of PyramidalModel."+coverageStore.toString());
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final PyramidalCoverageReference pm = (PyramidalCoverageReference) cv;
//----------------------- sample dimensions-----------------------------
final GridCoverageReader gridReader = gridCoverageRef.acquireReader();
final int imageIndex = gridCoverageRef.getImageIndex();
List<GridSampleDimension> coverageSampleDims = gridReader.getSampleDimensions(imageIndex);
if (!isDimensionsCompatible(pm, coverageSampleDims))
throw new DataStoreException("Incompatible GridSampleDimensions. " +
"Input coverage should have compatible GridSampleDimension with output Pyramid.");
pm.setSampleDimensions(coverageSampleDims);
//----------------------------------------------------------------------
final GridGeometry currentgridGeometry = gridReader.getGridGeometry(imageIndex);
//-- init listener ---
if (processListener != null) initListener(resolution_Per_Envelope, currentgridGeometry, processListener);
for (Envelope outEnv : resolution_Per_Envelope.keySet()) {
final CoordinateReferenceSystem crs = outEnv.getCoordinateReferenceSystem();
final int minOrdi0 = CRSUtilities.firstHorizontalAxis(crs);
final int minOrdi1 = minOrdi0 + 1;
final int outDim = crs.getCoordinateSystem().getDimension();
final DirectPosition upperLeft = new GeneralDirectPosition(crs);
upperLeft.setOrdinate(minOrdi0, outEnv.getMinimum(minOrdi0));
upperLeft.setOrdinate(minOrdi1, outEnv.getMaximum(minOrdi1));
//one pyramid for each CoordinateReferenceSystem.
final Pyramid pyram = getOrCreatePyramid(pm, crs);
// final CombineIterator itEnv = new CombineIterator(new GeneralEnvelope(outEnv));
final GridCombineIterator gitenv = new GridCombineIterator(new GeneralGridGeometry(currentgridGeometry));
while (gitenv.hasNext()) {
final Envelope gcEnv = gitenv.next();
//-- temporary reprojection
//-- try later to concatene gridtocrs + findmathtransform srcCrs -> outCrs
final GeneralEnvelope envDest = GeneralEnvelope.castOrCopy(Envelopes.transform(gcEnv, crs));
final GeneralEnvelope clipped = new GeneralEnvelope(envDest);
//set upperLeft ordinate
for (int d = 0; d < outDim; d++) {
if (d != minOrdi0 && d != minOrdi1) {
upperLeft.setOrdinate(d, envDest.getMedian(d));
} else {
//-- set horizontal crs part coordinates from out envelope into destination envelope
envDest.setRange(d, outEnv.getMinimum(d), outEnv.getMaximum(d));
// clip envelope 2D part
clipped.setRange(d, Math.max(clipped.getMinimum(d),outEnv.getMinimum(d)), Math.min(clipped.getMaximum(d),outEnv.getMaximum(d)));
}
}
final GridCoverageReadParam rp = new GridCoverageReadParam();
rp.setEnvelope(clipped);
rp.setDeferred(true);
resample(pm, pyram.getId(), gridReader, imageIndex, rp, resolution_Per_Envelope.get(outEnv), upperLeft, envDest, minOrdi0, minOrdi1, fillValue, processListener);
}
}
gridCoverageRef.recycle(gridReader);
if (processListener != null) processListener.completed(new ProcessEvent(fakeProcess, "Pyramid coverage builder successfully submitted.", 100));
}
/**
* Create and insert pyramid from {@code GridCoverage2D} source,
* in specified {@code PyramidalModel} at specified pyramidID.
*
* @param pm {@code PyramidalModel} in which insert pyramid tiles.
* @param pyramidID ID in which pyramid is inserted.
* @param gridCoverage2D source data of pyramid which will be inserted.
* @param scaleLevel scale values table for each pyramid level. Table length represent pyramid level number.
* @param upperLeft geographic upper left corner of pyramid will be inserted.
* @param envDest envelope which represent multi-dimensional slice of origin coverage envelope.
* @param widthAxis index of X direction from multi-dimensional coverage envelope.
* @param heightAxis index of Y direction from multi-dimensional coverage envelope.
* @param fillValue contains value use when pixel transformation is out of source image boundary.(should be {@code null}).
* Can be {@code null}. If {@code null} a default table value filled by zero value,
* with lenght equal to source coverage image band number is created.
*
* @throws NoninvertibleTransformException
* @throws FactoryException
* @throws TransformException
* @throws DataStoreException
*/
private void resample (final PyramidalCoverageReference pm, String pyramidID, GridCoverageReader coverageReader,
int imageIndex, GridCoverageReadParam readParam, double[] scaleLevel, DirectPosition upperLeft,
Envelope envDest, int widthAxis, int heightAxis, double[] fillValue, final ProcessListener processListener)
throws NoninvertibleTransformException, FactoryException, TransformException, DataStoreException, IOException {
final GeneralGridGeometry ggg = coverageReader.getGridGeometry(imageIndex);
if (!(ggg instanceof GridGeometry2D)) {
final IllegalArgumentException ex = new IllegalArgumentException("GridGeometry should be instance of GridGeometry2D");
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final GridGeometry2D gg2d = (GridGeometry2D) ggg;
final Envelope covEnv = gg2d.getEnvelope2D();
// work on pixels coordinates.
final double envWidth = envDest.getSpan(widthAxis);
final double envHeight = envDest.getSpan(heightAxis);
final double min0 = envDest.getMinimum(widthAxis);
final double max1 = envDest.getMaximum(heightAxis);
//MathTransform2D
CoordinateReferenceSystem envDestCRS2D = CRSUtilities.getCRS2D(envDest.getCoordinateReferenceSystem());
GeneralEnvelope envDest2D = GeneralEnvelope.castOrCopy(Envelopes.transform(envDest, envDestCRS2D));
final MathTransform destCrs_to_coverageCRS = CRS.findOperation(envDestCRS2D, gg2d.getCoordinateReferenceSystem2D(), null).getMathTransform();
final Dimension tileSize = new Dimension(tileWidth, tileHeight);
final GeneralEnvelope covEnvInDestCRS = Envelopes.transform(destCrs_to_coverageCRS.inverse(), covEnv);
final GeneralEnvelope clipEnv = ReferencingUtilities.intersectEnvelopes(covEnvInDestCRS, envDest2D);
//------------------- param resolution configuration -------------------
final int paramDim;
final CoordinateReferenceSystem paramCrs = readParam.getCoordinateReferenceSystem();
if (paramCrs != null) {
paramDim = paramCrs.getCoordinateSystem().getDimension();
} else if (readParam.getEnvelope() != null) {
paramDim = readParam.getEnvelope().getDimension();
} else {
//-- assume param in the same crs than coverage
readParam.setCoordinateReferenceSystem(ggg.getCoordinateReferenceSystem());
paramDim = ggg.getDimension();
}
final double[] res = new double[paramDim];
Arrays.fill(res, 1.0);
//----------------------------------------------------------------------
//-- one mosaic for each level scale
for (double pixelScal : scaleLevel) {
res[widthAxis] = res[heightAxis] = pixelScal;
//-- output image size
readParam.setResolution(res);
assert Utilities.equalsIgnoreMetadata(readParam.getCoordinateReferenceSystem(), ggg.getCoordinateReferenceSystem())
: "PyramidCoverageBuilder : requested CRS into GridCoverageReadParam must be same than Coverage";
final GridCoverage2D gridCoverage2D = (GridCoverage2D) coverageReader.read(imageIndex, readParam);//-- normaly with a gridGeometry2D --> gridCoverage2D
final RenderedImage baseImg = gridCoverage2D.getRenderedImage();
final MathTransform2D coverageCRS_to_grid = gridCoverage2D.getGridGeometry().getGridToCRS2D(PixelOrientation.CENTER).inverse();
final MathTransform destCrs_to_covGrid = MathTransforms.concatenate(destCrs_to_coverageCRS, coverageCRS_to_grid).inverse();
final double[] fill = getFillValue(gridCoverage2D, fillValue);
final double imgWidth = envWidth / pixelScal;
final double imgHeight = envHeight / pixelScal;
final double sx = envWidth / imgWidth;
final double sy = envHeight / imgHeight;
final MathTransform2D globalGridDest_to_crs = new AffineTransform2D(sx, 0, 0, -sy, min0, max1);
//-- mosaic size
final int nbrTileX = (int) Math.ceil(imgWidth / tileWidth);
final int nbrTileY = (int) Math.ceil(imgHeight / tileHeight);
//-- coverage extent on mosaic space
final GeneralEnvelope coverageExtent = Envelopes.transform(globalGridDest_to_crs.inverse(), clipEnv);
//-- coverage intersection tile index
final int startTileX = (int) coverageExtent.getMinimum(widthAxis) / tileWidth;
final int startTileY = (int) coverageExtent.getMinimum(heightAxis) / tileHeight;
final int endTileX = (int) (coverageExtent.getMaximum(widthAxis) + tileWidth - 1) / tileWidth;
final int endTileY = (int) (coverageExtent.getMaximum(heightAxis) + tileHeight - 1) / tileHeight;
final GridMosaic mosaic = getOrCreateMosaic(pm, pyramidID, new Dimension(nbrTileX, nbrTileY), tileSize, upperLeft, pixelScal);
final String mosaicId = mosaic.getId();
final AtomicInteger inc = new AtomicInteger();
final RenderedImage img = new BuildImage(
startTileX*tileWidth,
startTileY*tileHeight,
(endTileX-startTileX)*tileWidth,
(endTileY-startTileY)*tileHeight,
tileSize, baseImg, mosaic,
processListener,
inc,fill,destCrs_to_covGrid,
sx,sy,min0,max1
);
try{
pm.writeTiles(pyramidID, mosaicId, img, false, null);
}catch(ImagingOpException ex){
if(processListener!=null){
float prc = (float)niemeTile / globalTileNumber;
processListener.failed(new ProcessEvent(fakeProcess, "writing tiles", prc, ex));
}
throw new DataStoreException(ex.getMessage(), ex);
}
}
}
/**
* <p>Effectuate resampling, re-projection, tile cutting and insertion in datastore on {@link GridCoverage}.<br/><br/>
*
* Note : <br/>
* {@link GridGeometry} from {@link GridCoverage} must be instance of {@link GridGeometry2D}
* else a {@link IllegalArgumentException} will be thrown.<br/>
* fillValue parameter must have same lenght than pixel size from image within coverage.<br/>
* If fill value is {@code null} a table of zero value with appropriate lenght is use.
* </p>
* @param gridCoverage {@link GridCoverage} which will be stored.
* @param coverageStore {@link CoverageStore} where operation on {@link GridCoverage} are stored.
* @param coverageName name given to the set of operations results, performed on the coverage in the datastore.
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @param processListener {@link ProcessListener} to send state informations (should be null).
* @param monitor A progress monitor used for detecting a cancel request during the process. Can be {@code null}.
* @throws DataStoreException if tile writing throw exception.
* @throws TransformException if problems during resampling operation.
* @throws FactoryException if impossible to find {@code MathTransform} between two {@link CoordinateReferenceSystem}.
*/
public void create(GridCoverage gridCoverage, CoverageStore coverageStore, GenericName coverageName,
Map<Envelope, double[]> resolution_Per_Envelope, double[] fillValue,
ProcessListener processListener, ProgressMonitor monitor)
throws DataStoreException, TransformException, FactoryException, IOException {
ArgumentChecks.ensureNonNull("GridCoverage" , gridCoverage);
ArgumentChecks.ensureNonNull("output CoverageStore" , coverageStore);
ArgumentChecks.ensureNonNull("coverageName" , coverageName);
ArgumentChecks.ensureNonNull("resolution_Per_Envelope", resolution_Per_Envelope);
final GridGeometry gg = gridCoverage.getGridGeometry();
if (!(gg instanceof GridGeometry2D)) {
final IllegalArgumentException ex = new IllegalArgumentException("GridGeometry not instance of GridGeometry2D");
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final CoverageReference cv = getOrCreateCRef(coverageStore,coverageName);
if (!(cv instanceof PyramidalCoverageReference)) {
final IllegalArgumentException ex = new IllegalArgumentException("CoverageReference not instance of PyramidalCoverageReference");
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
if (monitor != null && monitor.isCanceled()) {
final CancellationException ex = new CancellationException();
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
if (processListener != null) initListener(resolution_Per_Envelope, gg, processListener);
final PyramidalCoverageReference pm = (PyramidalCoverageReference) cv;
//------------------------ add sampleDimension -------------------------
final int nbSampleDimension = gridCoverage.getNumSampleDimensions();
final ArrayList<GridSampleDimension> sampleList = new ArrayList<GridSampleDimension>();
for (int nbs = 0; nbs < nbSampleDimension; nbs++) {
final SampleDimension s = gridCoverage.getSampleDimension(nbs);
if (s instanceof GridSampleDimension) {
sampleList.add((GridSampleDimension) s);
} else {
//-- should never append for the moment the only sampleDimension type is GridSampleDimension.
throw new IllegalStateException("The only supported SampleDimension type is GridSampleDimension. Found : "+s.getClass());
}
}
if (!isDimensionsCompatible(pm, sampleList))
throw new DataStoreException("Incompatible GridSampleDimensions. " +
"Input coverage should have compatible GridSampleDimension with output Pyramid.");
pm.setSampleDimensions(sampleList);
//----------------------------------------------------------------------
//Image
fillValue = getFillValue((GridCoverage2D)gridCoverage, fillValue);
for (Envelope envDest : resolution_Per_Envelope.keySet()) {
if (monitor != null && monitor.isCanceled()) {
final CancellationException ex = new CancellationException();
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final CoordinateReferenceSystem crs = envDest.getCoordinateReferenceSystem();
final int minOrdi0 = CoverageUtilities.getMinOrdinate(crs);
final int minOrdi1 = minOrdi0 + 1;
final DirectPosition upperLeft = new GeneralDirectPosition(crs);
upperLeft.setOrdinate(minOrdi0, envDest.getMinimum(minOrdi0));
upperLeft.setOrdinate(minOrdi1, envDest.getMaximum(minOrdi1));
//one pyramid for each CoordinateReferenceSystem.
final Pyramid pyram = getOrCreatePyramid(pm, crs);
resample(pm, pyram.getId(), ((GridCoverage2D)gridCoverage), resolution_Per_Envelope.get(envDest), upperLeft, envDest, minOrdi0, minOrdi1, fillValue, processListener);
}
if (processListener != null) processListener.completed(new ProcessEvent(fakeProcess, "Pyramid coverage builder successfully submitted.", 100));
}
/**
* <p>Effectuate resampling, re-projection, tile cutting and insertion in datastore on {@link GridCoverage} from {@link GridCoverageReader}.<br/><br/>
*
* Note : <br/>
* {@link GridGeometry} from {@link GridCoverage} must be instance of {@link GridGeometry2D}
* else a {@link IllegalArgumentException} will be thrown.<br/>
* fillValue parameter must have same lenght than pixel size from image within coverage.<br/>
* If fill value is {@code null} a table of zero value with appropriate lenght is use.
* </p>
*
* @param reader {@link GridCoverageReader} which contain {@link GridCoverage} will be stored.
* @param coverageStore {@link CoverageStore} where operation on {@link GridCoverage} are stored.
* @param coverageName name given to the set of operations results, performed on the coverage in the datastore.
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param fillValue contains value use when pixel transformation is out of source image boundary.(should be {@code null}).
* @throws DataStoreException if tile writing throw exception.
* @throws TransformException if problems during resampling operation.
* @throws FactoryException if impossible to find {@code MathTransform} between two {@link CoordinateReferenceSystem}.
*/
public void create(GridCoverageReader reader, CoverageStore coverageStore, GenericName coverageName,
Map<Envelope, double[]> resolution_Per_Envelope, double[] fillValue)
throws DataStoreException, TransformException, FactoryException, IOException {
create(reader, coverageStore, coverageName, resolution_Per_Envelope, fillValue, null);
}
/**
* <p>Effectuate resampling, re-projection, tile cutting and insertion in datastore on {@link GridCoverage} from {@link GridCoverageReader}.<br/><br/>
*
* Note : <br/>
* {@link GridGeometry} from {@link GridCoverage} must be instance of {@link GridGeometry2D}
* else a {@link IllegalArgumentException} will be thrown.<br/>
* fillValue parameter must have same lenght than pixel size from image within coverage.<br/>
* If fill value is {@code null} a table of zero value with appropriate lenght is use.
* </p>
*
* @param reader {@link GridCoverageReader} which contain {@link GridCoverage} will be stored.
* @param coverageStore {@link CoverageStore} where operation on {@link GridCoverage} are stored.
* @param coverageName name given to the set of operations results, performed on the coverage in the datastore.
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param fillValue contains value use when pixel transformation is out of source image boundary.
* @param processListener {@link ProcessListener} to send state informations (should be null).
* @throws DataStoreException if tile writing throw exception.
* @throws TransformException if problems during resampling operation.
* @throws FactoryException if impossible to find {@code MathTransform} between two {@link CoordinateReferenceSystem}.
*/
public void create(GridCoverageReader reader, CoverageStore coverageStore, GenericName coverageName,
Map<Envelope, double[]> resolution_Per_Envelope, double[] fillValue, ProcessListener processListener)
throws DataStoreException, TransformException, FactoryException, IOException {
ArgumentChecks.ensureNonNull("GridCoverageReader" , reader);
ArgumentChecks.ensureNonNull("output CoverageStore" , coverageStore);
ArgumentChecks.ensureNonNull("coverageName" , coverageName);
ArgumentChecks.ensureNonNull("resolution_Per_Envelope", resolution_Per_Envelope);
final GridGeometry currentGridGeometry = reader.getGridGeometry(0);
if (processListener != null) initListener(resolution_Per_Envelope, currentGridGeometry, processListener);
final GridCoverageReadParam rp = new GridCoverageReadParam();
//one coverageReference for each reader.
final CoverageReference cv = getOrCreateCRef(coverageStore,coverageName);
if (!(cv instanceof PyramidalCoverageReference)) {
final IllegalArgumentException ex = new IllegalArgumentException("CoverageStore parameter should be instance of PyramidalModel."+coverageStore.toString());
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final PyramidalCoverageReference pm = (PyramidalCoverageReference) cv;
//----------------------- sample dimensions-----------------------------
List<GridSampleDimension> coverageSampleDims = reader.getSampleDimensions(0);
if (!isDimensionsCompatible(pm, coverageSampleDims))
throw new DataStoreException("Incompatible GridSampleDimensions. " +
"Input coverage should have compatible GridSampleDimension with output Pyramid.");
pm.setSampleDimensions(coverageSampleDims); //-- default image index
//----------------------------------------------------------------------
for (Envelope outEnv : resolution_Per_Envelope.keySet()) {
final CoordinateReferenceSystem crs = outEnv.getCoordinateReferenceSystem();
final int minOrdi0 = CoverageUtilities.getMinOrdinate(crs);
final int minOrdi1 = minOrdi0 + 1;
final int outDim = crs.getCoordinateSystem().getDimension();
final DirectPosition upperLeft = new GeneralDirectPosition(crs);
upperLeft.setOrdinate(minOrdi0, outEnv.getMinimum(minOrdi0));
upperLeft.setOrdinate(minOrdi1, outEnv.getMaximum(minOrdi1));
//one pyramid for each CoordinateReferenceSystem.
final Pyramid pyram = getOrCreatePyramid(pm, crs);
// final CombineIterator itEnv = new CombineIterator(new GeneralEnvelope(outEnv));
final GridCombineIterator gitenv = new GridCombineIterator(new GeneralGridGeometry(currentGridGeometry));
while (gitenv.hasNext()) {
final GeneralEnvelope envDest = GeneralEnvelope.castOrCopy(Envelopes.transform(gitenv.next(), crs));
for (int d = 0; d < outDim; d++) {
if (d != minOrdi0 && d != minOrdi1) {
//set upperLeft ordinate
upperLeft.setOrdinate(d, envDest.getMedian(d));
} else {
//-- set horizontal crs part coordinates from out envelope into destination envelope
envDest.setRange(d, outEnv.getMinimum(d), outEnv.getMaximum(d));
}
}
rp.clear();
rp.setEnvelope(envDest);
rp.setDeferred(true);
final GridCoverage gridCoverage = reader.read(0, rp);
final GridGeometry gg = gridCoverage.getGridGeometry();
if (!(gg instanceof GridGeometry2D)) {
final IllegalArgumentException ex = new IllegalArgumentException("GridGeometry should be instance of GridGeometry2D");
if (processListener != null) processListener.failed(new ProcessEvent(fakeProcess, "", 0, ex));
throw ex;
}
final GridCoverage2D gridCoverage2D = (GridCoverage2D) gridCoverage;
resample(pm, pyram.getId(), gridCoverage2D, resolution_Per_Envelope.get(outEnv), upperLeft, envDest, minOrdi0, minOrdi1, fillValue, processListener);
}
}
if (processListener != null) processListener.completed(new ProcessEvent(fakeProcess, "Pyramid coverage builder successfully submitted.", 100));
}
/**
* Create and insert pyramid from {@code GridCoverage2D} source,
* in specified {@code PyramidalModel} at specified pyramidID.
*
* @param pm {@code PyramidalModel} in which insert pyramid tiles.
* @param pyramidID ID in which pyramid is inserted.
* @param gridCoverage2D source data of pyramid which will be inserted.
* @param scaleLevel scale values table for each pyramid level. Table length represent pyramid level number.
* @param upperLeft geographic upper left corner of pyramid will be inserted.
* @param envDest envelope which represent multi-dimensional slice of origin coverage envelope.
* @param widthAxis index of X direction from multi-dimensional coverage envelope.
* @param heightAxis index of Y direction from multi-dimensional coverage envelope.
* @param fillValue contains value use when pixel transformation is out of source image boundary.(should be {@code null}).
* Can be {@code null}. If {@code null} a default table value filled by zero value,
* with lenght equal to source coverage image band number is created.
*
* @throws NoninvertibleTransformException
* @throws FactoryException
* @throws TransformException
* @throws DataStoreException
*/
private void resample (final PyramidalCoverageReference pm, String pyramidID, GridCoverage2D gridCoverage2D, double[] scaleLevel,
DirectPosition upperLeft, Envelope envDest, int widthAxis, int heightAxis, double[] fillValue, final ProcessListener processListener)
throws NoninvertibleTransformException, FactoryException, TransformException, DataStoreException, IOException {
final GridGeometry2D gg2d = gridCoverage2D.getGridGeometry();
final Envelope covEnv = gg2d.getEnvelope2D();
final RenderedImage baseImg = gridCoverage2D.getRenderedImage();
// work on pixels coordinates.
final MathTransform2D coverageCRS_to_grid = gg2d.getGridToCRS2D(PixelOrientation.CENTER).inverse();
final double envWidth = envDest.getSpan(widthAxis);
final double envHeight = envDest.getSpan(heightAxis);
// final int nbBand = baseImg.getSampleModel().getNumBands();
final double[] fill = getFillValue(gridCoverage2D, fillValue);
// final int dataType = baseImg.getSampleModel().getDataType();
final double min0 = envDest.getMinimum(widthAxis);
final double max1 = envDest.getMaximum(heightAxis);
//-- MathTransform2D
CoordinateReferenceSystem envDestCRS2D = CRSUtilities.getCRS2D(envDest.getCoordinateReferenceSystem());
GeneralEnvelope envDest2D = GeneralEnvelope.castOrCopy(Envelopes.transform(envDest, envDestCRS2D));
final MathTransform destCrs_to_coverageCRS = CRS.findOperation(envDestCRS2D, gridCoverage2D.getCoordinateReferenceSystem2D(), null).getMathTransform();
final MathTransform destCrs_to_covGrid = MathTransforms.concatenate(destCrs_to_coverageCRS, coverageCRS_to_grid).inverse();
final Dimension tileSize = new Dimension(tileWidth, tileHeight);
final GeneralEnvelope covEnvInDestCRS = Envelopes.transform(destCrs_to_coverageCRS.inverse(), covEnv);
final GeneralEnvelope clipEnv = ReferencingUtilities.intersectEnvelopes(covEnvInDestCRS, envDest2D);
//one mosaic for each level scale
for (double pixelScal : scaleLevel) {
//output image size
final double imgWidth = envWidth / pixelScal;
final double imgHeight = envHeight / pixelScal;
final double sx = envWidth / imgWidth;
final double sy = envHeight / imgHeight;
final MathTransform2D globalGridDest_to_crs = new AffineTransform2D(sx, 0, 0, -sy, min0, max1);
//mosaic size
final int nbrTileX = (int)Math.ceil(imgWidth/tileWidth);
final int nbrTileY = (int)Math.ceil(imgHeight/tileHeight);
//coverage extent on mosaic space
final GeneralEnvelope coverageExtent = Envelopes.transform(globalGridDest_to_crs.inverse(), clipEnv);
//coverage intersection tile index
final int startTileX = (int)coverageExtent.getMinimum(widthAxis) / tileWidth;
final int startTileY = (int)coverageExtent.getMinimum(heightAxis) / tileHeight;
final int endTileX = (int)(coverageExtent.getMaximum(widthAxis) + tileWidth - 1) / tileWidth;
final int endTileY = (int)(coverageExtent.getMaximum(heightAxis) + tileHeight - 1) / tileHeight;
final GridMosaic mosaic = getOrCreateMosaic(pm, pyramidID, new Dimension(nbrTileX, nbrTileY), tileSize, upperLeft, pixelScal);
final String mosaicId = mosaic.getId();
final AtomicInteger inc = new AtomicInteger();
final AtomicReference<Exception> exps = new AtomicReference<>();
final RenderedImage img = new BuildImage(
startTileX*tileWidth,
startTileY*tileHeight,
(endTileX-startTileX)*tileWidth,
(endTileY-startTileY)*tileHeight,
tileSize, baseImg, mosaic,
processListener,
inc,fill,destCrs_to_covGrid,
sx,sy,min0,max1
);
try{
pm.writeTiles(pyramidID, mosaicId, img, false, null);
}catch(ImagingOpException ex){
if(processListener!=null){
float prc = (float)niemeTile / globalTileNumber;
processListener.failed(new ProcessEvent(fakeProcess, "writing tiles", prc, ex));
}
throw new DataStoreException(ex.getMessage(), ex);
}
pm.writeTiles(pyramidID, mosaicId, img, false, null);
}
}
/**
* Extract BufferedImage from TileReference.
*
* @param tile TileReference, should not be null.
* @return tile BufferedImage.
* @throws IOException if error on reading image from tile ImageReader.
*/
private BufferedImage getImageFromTile(TileReference tile) throws IOException {
ArgumentChecks.ensureNonNull("tile", tile);
final BufferedImage sourceImg;
ImageReader reader = null;
try {
if (tile.getInput() instanceof BufferedImage) {
sourceImg = (BufferedImage) tile.getInput();
} else {
final int imgIdx = tile.getImageIndex();
reader = tile.getImageReader();
sourceImg = tile.getImageReader().read(imgIdx);
}
} finally {
if (reader != null) {
XImageIO.dispose(reader);
}
}
return sourceImg;
}
private static double[] getFillValue(GridCoverage2D gridCoverage2D, double[] fillValue){
//-- calculate fill values
if (fillValue == null) {
final GridSampleDimension[] dimensions = gridCoverage2D.getSampleDimensions();
final int nbBand = dimensions.length;
fillValue = new double[nbBand];
Arrays.fill(fillValue, Double.NaN);
for(int i=0;i<nbBand;i++){
final double[] nodata = dimensions[i].geophysics(true).getNoDataValues();
if (nodata != null && nodata.length > 0){
fillValue[i] = nodata[0];
}
}
}
return fillValue;
}
/**
* Initialize attribut to {@link ProcessListener} use.
*
* @param resolution_Per_Envelope reprojection and resampling attibuts.
* @param processListener
*/
private void initListener(final Map<Envelope, double[]> resolution_Per_Envelope, final GridGeometry currentGridGeom,
final ProcessListener processListener) throws TransformException {
assert resolution_Per_Envelope != null : "resolution_Per_Envelope should not be null";
assert processListener != null : "processListener should not be null";
globalTileNumber = 0;
niemeTile = 0;
for (Envelope outEnv : resolution_Per_Envelope.keySet()) {
final CoordinateReferenceSystem crs = outEnv.getCoordinateReferenceSystem();
final int minOrdi0 = CoverageUtilities.getMinOrdinate(crs);
final int minOrdi1 = minOrdi0 + 1;
final int outDim = crs.getCoordinateSystem().getDimension();
// final CombineIterator itEnv = new CombineIterator(new GeneralEnvelope(outEnv));
final GridCombineIterator gitEnv = new GridCombineIterator(new GeneralGridGeometry(currentGridGeom));
while (gitEnv.hasNext()) {
final Envelope envGit = gitEnv.next();
final GeneralEnvelope envDest = GeneralEnvelope.castOrCopy(Envelopes.transform(envGit, crs));
for (int d = 0; d < outDim; d++) {
if (d == minOrdi0 || d == minOrdi1) {
//-- set horizontal crs part coordinates from out envelope into destination envelope
envDest.setRange(d, outEnv.getMinimum(d), outEnv.getMaximum(d));
}
}
//-- set horizontal crs part coordinates from out envelope into destination envelope
for (double pixelScal : resolution_Per_Envelope.get(outEnv)) {
final int nbrtx = (int) Math.ceil((envDest.getSpan(minOrdi0) / pixelScal) / tileWidth);
final int nbrty = (int) Math.ceil((envDest.getSpan(minOrdi1) / pixelScal) / tileHeight);
globalTileNumber += nbrtx * nbrty;
}
}
}
processListener.started(new ProcessEvent(fakeProcess, "0/"+globalTileNumber, 0));
}
/**
* Search and return a {@link CoverageReference} in a {@link CoverageStore} from its {@link GenericName}.<br/>
* If it doesn't exist a {@link CoverageReference} is created, added in {@link CoverageStore} parameter and returned.
*
* @param coverageStore
* @param coverageName
* @return a {@link CoverageReference} in a {@link CoverageStore} from its {@link GenericName}.
* @throws DataStoreException
*/
private CoverageReference getOrCreateCRef(CoverageStore coverageStore, GenericName coverageName) throws DataStoreException {
CoverageReference cv = null;
for (GenericName n : coverageStore.getNames()) {
if (n.tip().toString().equals(coverageName.tip().toString())) {
cv = coverageStore.getCoverageReference(n);
}
}
if (cv == null) {
cv = coverageStore.create(coverageName);
}
return cv;
}
/**
* Search and return a {@link Pyramid} in a {@link PyramidalCoverageReference} from its {@link CoordinateReferenceSystem} properties.<br/>
* If it doesn't exist a {@link Pyramid} is created, added in {@link PyramidalCoverageReference} parameter and returned.
*
* @param pm
* @param crs
* @return a {@link Pyramid} in a {@link PyramidalCoverageReference} from its {@link CoordinateReferenceSystem} properties.
* @throws DataStoreException
*/
public static synchronized Pyramid getOrCreatePyramid(final PyramidalCoverageReference pm, final CoordinateReferenceSystem crs) throws DataStoreException {
Pyramid pyramid = null;
for (Pyramid p : pm.getPyramidSet().getPyramids()) {
if (Utilities.equalsIgnoreMetadata(p.getCoordinateReferenceSystem(), crs)) {
pyramid = p;
break;
}
}
if (pyramid == null) {
pyramid = pm.createPyramid(crs);
}
return pyramid;
}
public static synchronized GridMosaic getOrCreateMosaic(final PyramidalCoverageReference pm,
String pyramidID, Dimension gridsize, Dimension tileSize, DirectPosition upperLeft, double pixelScal) throws DataStoreException {
final Pyramid pyramid = pm.getPyramidSet().getPyramid(pyramidID);
for(GridMosaic gm : pyramid.getMosaics()){
if(gm.getScale() == pixelScal && Arrays.equals(upperLeft.getCoordinate(),gm.getUpperLeftCorner().getCoordinate())){
return gm;
}
}
return pm.createMosaic(pyramidID, gridsize, tileSize, upperLeft, pixelScal);
}
/**
* Returns a {@link GridMosaic} which already exist.<br><br>
* <strong>
* Note : an exception is thrown if tile does not exist.
* </strong>
*
* @param pm {@link PyramidalCoverageReference} where the requested {@link GridMosaic} is looking for.
* @param pyramidID {@link Pyramid} identifier of the requested {@link GridMosaic}.
* @param envDest requested {@link Envelope} in relation with needed {@link GridMosaic}.
* @param pixelScal requested scale in relation with needed {@link GridMosaic}.
* @return a {@link GridMosaic} which already exist.
* @throws DataStoreException if pyramid does not match with the pyramid_ID or
* also if requested {@link GridMosaic} was not found.
*/
public static synchronized GridMosaic getMosaic(final PyramidalCoverageReference pm,
String pyramidID, Envelope envDest, double pixelScal) throws DataStoreException {
final Pyramid pyramid = pm.getPyramidSet().getPyramid(pyramidID);
for (GridMosaic gm : pyramid.getMosaics()) {
if (gm.getScale() == pixelScal) {
final GeneralEnvelope mosGenEnv = new GeneralEnvelope(gm.getEnvelope());
if (mosGenEnv.intersects(envDest, true)) { //-- return a mosaic only if they have more than just touch intersection
return gm;
}
}
}
throw new DataStoreException("getOrCreateMosaic : with reuse tile. No already built mosaic can contains new data.");
}
/**
* Check if two GridSampleDimension list are compatible.
* GridSampleDimension list are compatible if they have same number of GridSampleDimension
* and GridSampleDimension are in same order and also compatible.
*
* Test is also considered valid if the pyramid GridSampleDimension list is null or empty
* in case of the pyramid have just been created.
*
* @param pyramidRef pyramid reference
* @param coverageSampleDims input coverage SampleDimension list
* @return true if compatible, false otherwise
* @throws DataStoreException if an error occurs during pyramid SampleDimension reading.
*/
private boolean isDimensionsCompatible(PyramidalCoverageReference pyramidRef, List<GridSampleDimension> coverageSampleDims)
throws DataStoreException {
final List<GridSampleDimension> pyramidSampleDims = pyramidRef.getSampleDimensions();
// pyramidSampleDims list is considered as valid in case of the pyramid have just been created.
if (pyramidSampleDims == null || pyramidSampleDims.isEmpty()) {
return true;
}
assert coverageSampleDims != null && !coverageSampleDims.isEmpty() : "coverageSampleDims should not be null or empty";
//compare size
if (pyramidSampleDims.size() != coverageSampleDims.size()) return false;
final int nbDims = pyramidSampleDims.size();
for (int i = 0; i < nbDims; i++) {
final GridSampleDimension pyramidDim = pyramidSampleDims.get(i);
final GridSampleDimension coverageDim = coverageSampleDims.get(i);
if (!isDimensionCompatible(pyramidDim, coverageDim)) return false;
}
return true;
}
/**
* Compare two GridSampleDimension.
* Check noData list, ColorModel pixelSize and ColorSpace type.
*
* TODO compare categories.
*
* @param gsd1 first GridSampleDimension
* @param gsd2 second GridSampleDimension
* @return true if compatible, false otherwise
*/
private boolean isDimensionCompatible(final GridSampleDimension gsd1, final GridSampleDimension gsd2) {
ArgumentChecks.ensureNonNull("gsd1", gsd1);
ArgumentChecks.ensureNonNull("gsd2", gsd2);
NumberRange range1 = gsd1.getRange();
NumberRange range2 = gsd2.getRange();
if (range1 == null) return range2 == null;
if (!range1.containsAny(range2)) return false;
//compare noData
double[] pNoData = gsd1.getNoDataValues();
double[] cNoData = gsd2.getNoDataValues();
if (!Arrays.equals(pNoData, cNoData)) return false;
//compare pixelSize and ColorSpace type
final ColorModel pCM = gsd1.getColorModel();
final ColorModel cCM = gsd2.getColorModel();
if (pCM != null && cCM != null) {
if (pCM.getPixelSize() != cCM.getPixelSize() ||
pCM.getColorSpace().getType() != cCM.getColorSpace().getType()) {
return false;
}
}
//TODO compare categories
return true;
}
/**
* Inner class that extend {@link AbstractLargeRenderedImage#getTile(int, int)}
* that resample on the fly mosaic tiles.
*/
private class BuildImage extends AbstractLargeRenderedImage{
private final ProcessListener processListener;
private final AtomicInteger lastProc;
private final RenderedImage baseImg;
private final GridMosaic mosaic;
private final double[] fill;
private final MathTransform destCrs_to_covGrid;
private final double[] affArgs;
private BuildImage(int minX, int minY, int width, int height, Dimension tileSize,
RenderedImage baseImg, GridMosaic mosaic, ProcessListener processListener,
AtomicInteger lastProc, double[] fill, MathTransform destCrs_to_covGrid,
double... affArgs){
super(minX,minY,width,height,tileSize,0,0, baseImg.getSampleModel(), baseImg.getColorModel());
this.baseImg = baseImg;
this.mosaic = mosaic;
this.processListener = processListener;
this.lastProc = lastProc;
this.fill = fill;
this.destCrs_to_covGrid = destCrs_to_covGrid;
this.affArgs = affArgs;
}
// @Override
// public SampleModel getSampleModel() {
// return baseImg.getSampleModel();
// }
@Override
public Raster getTile(int cTX, int cTY) {
final int destMinX = cTX * getTileWidth();
final int destMinY = cTY * getTileHeight();
boolean noFill = false;
try{
WritableRenderedImage destImg;
if (reuseTiles && !mosaic.isMissing(cTX, cTY)) {
TileReference tile = mosaic.getTile(cTX, cTY, null);
destImg = getImageFromTile(tile);
noFill = true;
} else {
destImg = BufferedImages.createImage(tileWidth, tileHeight, baseImg);
//-- ensure fill value is set.
ImageUtilities.fill(destImg, fill[0]);
}
if (processListener != null) {
niemeTile++;
//do not send too much events, one every percent
int prc = (niemeTile * 100 / globalTileNumber);
if(prc!= lastProc.getAndSet(prc)){
processListener.progressing(new ProcessEvent(fakeProcess, (niemeTile) + "/" + globalTileNumber, prc));
}
}
//-- dest grid --> dest envelope coordinate --> base envelope --> base grid
//-- concatene : dest grid_to_crs, dest_crs_to_coverageCRS, coverageCRS_to_grid coverage
final MathTransform2D gridDest_to_crs = new AffineTransform2D(affArgs[0], 0, 0, -affArgs[1], affArgs[2] + affArgs[0] * (destMinX + 0.5), affArgs[3] - affArgs[1] * (destMinY + 0.5)).inverse();
final MathTransform mt = MathTransforms.concatenate(destCrs_to_covGrid, gridDest_to_crs);
final Resample resample = new Resample(mt.inverse(), destImg, baseImg, interpolationCase, lanczosWindow,
ResampleBorderComportement.FILL_VALUE, (noFill ? null : fill));
resample.fillImage();
return destImg.getTile(0, 0);
}catch(Exception ex){
throw new ImagingOpException(ex.getMessage());
}
}
};
}