/* * Geotoolkit - 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.processing.coverage.copy; import org.apache.sis.geometry.GeneralDirectPosition; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.image.io.XImageIO; import org.geotoolkit.storage.coverage.CoverageReference; import org.geotoolkit.storage.coverage.CoverageStore; import org.geotoolkit.storage.coverage.GridMosaic; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.storage.coverage.Pyramid; import org.geotoolkit.storage.coverage.PyramidSet; import org.geotoolkit.storage.coverage.PyramidalCoverageReference; import org.geotoolkit.storage.coverage.TileReference; import org.geotoolkit.coverage.grid.GeneralGridGeometry; 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.opengis.util.GenericName; import org.geotoolkit.parameter.Parameters; import org.geotoolkit.processing.AbstractProcess; import org.geotoolkit.process.Process; import org.geotoolkit.process.ProcessException; import org.geotoolkit.processing.coverage.reducetodomain.ReduceToDomainDescriptor; import org.geotoolkit.processing.coverage.straighten.StraightenDescriptor; import org.geotoolkit.temporal.object.TemporalUtilities; import org.opengis.geometry.Envelope; import org.opengis.metadata.spatial.PixelOrientation; import org.opengis.parameter.ParameterValueGroup; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.crs.ImageCRS; import org.opengis.referencing.operation.TransformException; import javax.imageio.ImageReader; import java.awt.*; import java.awt.geom.AffineTransform; import java.awt.image.BufferedImage; import java.awt.image.IndexColorModel; import java.awt.image.RenderedImage; import java.io.IOException; import java.util.AbstractCollection; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.concurrent.BlockingQueue; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicLong; import java.util.logging.Level; import org.geotoolkit.coverage.combineIterator.GridCombineIterator; import org.apache.sis.util.logging.Logging; import static org.geotoolkit.parameter.Parameters.value; import static org.geotoolkit.processing.coverage.copy.CopyCoverageStoreDescriptor.ERASE; import static org.geotoolkit.processing.coverage.copy.CopyCoverageStoreDescriptor.INSTANCE; import static org.geotoolkit.processing.coverage.copy.CopyCoverageStoreDescriptor.REDUCE_TO_DOMAIN; import static org.geotoolkit.processing.coverage.copy.CopyCoverageStoreDescriptor.STORE_IN; import static org.geotoolkit.processing.coverage.copy.CopyCoverageStoreDescriptor.STORE_OUT; import org.geotoolkit.utility.parameter.ParametersExt; /** * Copy a {@linkplain CoverageStore coverage store} into another one, that supports * a {@linkplain PyramidalModel pyramid model}. * * @author Cédric Briançon (Geomatys) * @author Johann Sorel (Geomatys) * @module */ public class CopyCoverageStoreProcess extends AbstractProcess { /** * Default constructor */ public CopyCoverageStoreProcess(final ParameterValueGroup input) { super(INSTANCE,input); } /** * * @param inStore input coverage store * @param outStore output coverage store * @param erase erase output data before insert * @param reduce reduce to data domain */ public CopyCoverageStoreProcess(CoverageStore inStore,CoverageStore outStore,boolean erase, boolean reduce){ super(INSTANCE, asParameters(inStore,outStore,erase,reduce)); } private static ParameterValueGroup asParameters(CoverageStore inStore,CoverageStore outStore,boolean erase, boolean reduce){ final ParameterValueGroup params = CopyCoverageStoreDescriptor.INPUT_DESC.createValue(); ParametersExt.getOrCreateValue(params, CopyCoverageStoreDescriptor.STORE_IN.getName().getCode()).setValue(inStore); ParametersExt.getOrCreateValue(params, CopyCoverageStoreDescriptor.STORE_OUT.getName().getCode()).setValue(outStore); ParametersExt.getOrCreateValue(params, CopyCoverageStoreDescriptor.ERASE.getName().getCode()).setValue(erase); ParametersExt.getOrCreateValue(params, CopyCoverageStoreDescriptor.REDUCE_TO_DOMAIN.getName().getCode()).setValue(reduce); return params; } /** * Execute process now. * * @throws ProcessException */ public void executeNow() throws ProcessException { execute(); } /** * {@inheritDoc} */ @Override protected void execute() throws ProcessException { final CoverageStore inStore = value(STORE_IN, inputParameters); final CoverageStore outStore = value(STORE_OUT, inputParameters); final Boolean erase = value(ERASE, inputParameters); final Boolean reduce = value(REDUCE_TO_DOMAIN, inputParameters); try { final float size = inStore.getNames().size(); int inc = 0; for(GenericName n : inStore.getNames()){ fireProgressing("Copying "+n+".", (int)((inc*100f)/size), false); final CoverageReference inRef = inStore.getCoverageReference(n); final GenericName name = inRef.getName(); if (erase) { outStore.delete(name); } final CoverageReference outRef = outStore.create(name); if(inRef instanceof PyramidalCoverageReference && outRef instanceof PyramidalCoverageReference){ savePMtoPM((PyramidalCoverageReference)inRef, (PyramidalCoverageReference)outRef); }else if(outRef instanceof PyramidalCoverageReference){ savePlainToPM(inRef, (PyramidalCoverageReference)outRef, reduce); }else{ throw new DataStoreException("The given coverage reference is not a pyramidal model, " + "this process only work with this kind of model."); } inc++; } } catch (DataStoreException ex) { throw new ProcessException(ex.getLocalizedMessage(), this, ex); } catch (TransformException ex) { throw new ProcessException(ex.getLocalizedMessage(), this, ex); } } /** * If both source and target are pyramid model, we can copy each tiles. */ private void savePMtoPM(final PyramidalCoverageReference inPM, final PyramidalCoverageReference outPM) throws DataStoreException{ final PyramidSet inPS = inPM.getPyramidSet(); final List<GridSampleDimension> sampleDimensions = inPM.getSampleDimensions(); if(sampleDimensions != null){ outPM.setSampleDimensions(sampleDimensions); } //count total number of tiles long nb = 0; for(final Pyramid inPY : inPS.getPyramids()){ for(final GridMosaic inGM : inPY.getMosaics()){ nb += inGM.getGridSize().height*inGM.getGridSize().width; } } final long total = nb; final int processors = Runtime.getRuntime().availableProcessors(); final ExecutorService es = Executors.newFixedThreadPool(processors); try{ final long before = System.currentTimeMillis(); final AtomicLong count = new AtomicLong(); //copy pyramids for(final Pyramid inPY : inPS.getPyramids()){ final Pyramid outPY = outPM.createPyramid(inPY.getCoordinateReferenceSystem()); //copy mosaics for(final GridMosaic inGM : inPY.getMosaics()){ final Dimension gridDimension = inGM.getGridSize(); final GridMosaic outGM = outPM.createMosaic(outPY.getId(), gridDimension, inGM.getTileSize(), inGM.getUpperLeftCorner(), inGM.getScale()); //collection of all tile points final Collection<Point> allPoints = new AbstractCollection<Point>() { @Override public Iterator<Point> iterator() { return new Iterator<Point>() { int x = 0; int y = 0; Point next = null; @Override public boolean hasNext() { findNext(); return next != null; } @Override public Point next() { findNext(); Point cp = next; next = null; if(cp == null){ throw new NoSuchElementException("no more element"); } return cp; } private void findNext(){ if(next != null) return; if(x>=gridDimension.width){ x=0; y++; } if(y>=gridDimension.height){ //reached the end return; } next = new Point(x, y); x++; } @Override public void remove() { } }; } @Override public int size() { return gridDimension.height*gridDimension.width; } }; final BlockingQueue<Object> queue = inGM.getTiles(allPoints, null); while(true){ final Object obj; try { obj = queue.take(); } catch (InterruptedException ex) { Logging.getLogger("org.geotoolkit.processing.coverage.copy").log(Level.SEVERE, null, ex); continue; } if(obj == GridMosaic.END_OF_QUEUE){ break; }else if(obj == null){ continue; } final TileReference inTR = (TileReference) obj; final int x = inTR.getPosition().x; final int y = inTR.getPosition().y; ImageReader inReader = null; try{ Object input = inTR.getInput(); RenderedImage image; if(input instanceof RenderedImage){ image = (RenderedImage) input; if(image.getColorModel() instanceof IndexColorModel){ final BufferedImage bg = new BufferedImage(image.getWidth(), image.getHeight(), BufferedImage.TYPE_INT_ARGB); bg.createGraphics().drawRenderedImage(image, new AffineTransform()); image = bg; } }else{ inReader = inTR.getImageReader(); image = inReader.read(inTR.getImageIndex()); } final RenderedImage img = image; es.submit(new Runnable() { @Override public void run() { try { outPM.writeTile(outPY.getId(), outGM.getId(), x, y, img); } catch (DataStoreException ex) { CopyCoverageStoreProcess.this.fireWarningOccurred(ex.getMessage(), 0, ex); return; } final long inc = count.incrementAndGet(); final long current = System.currentTimeMillis(); final long oneTileTime = (current-before) / inc; final long remaining = (total-inc) * oneTileTime; final String remtext = TemporalUtilities.durationToString(remaining); fireProgressing(inc+" / "+total+ " ("+remtext+")", inc/total, false); } }); }catch(IOException ex){ throw new DataStoreException(ex); }finally{ //dispose reader and substream XImageIO.disposeSilently(inReader); } } } } }finally{ es.shutdown(); } } /** * Save a {@linkplain CoverageReference coverage} into a {@linkplain CoverageStore coverage store}. * * @param outStore Coverage store in which to copy values. * @param inRef Coverage to store. * @param erase {@code True} if the data must be erased. * * @throws DataStoreException * @throws TransformException */ private void savePlainToPM(final CoverageReference inRef, final PyramidalCoverageReference outPM, Boolean reduce) throws DataStoreException, TransformException, ProcessException { if(reduce == null) reduce = Boolean.TRUE; final GridCoverageReader reader = inRef.acquireReader(); final int imageIndex = inRef.getImageIndex(); final GeneralGridGeometry globalGeom = reader.getGridGeometry(imageIndex); final CoordinateReferenceSystem crs = globalGeom.getCoordinateReferenceSystem(); final GenericName name = inRef.getName(); if(crs instanceof ImageCRS){ //image is not georeferenced, we can't store it. fireWarningOccurred("Image "+name+" does not have a CoordinateReferenceSystem, insertion is skipped.", 0, null); inRef.recycle(reader); return; } //create sampleDimensions bands final List<GridSampleDimension> sampleDimensions = reader.getSampleDimensions(imageIndex); outPM.setSampleDimensions(sampleDimensions); final Pyramid pyramid = outPM.createPyramid(crs); // save all possible envelope slice combinations in a separate mosaic. final GridCombineIterator gridCIte = new GridCombineIterator(globalGeom); while (gridCIte.hasNext()) { GeneralEnvelope env = GeneralEnvelope.castOrCopy(gridCIte.next()); saveMosaic(outPM, pyramid, reader, imageIndex, env, reduce); } inRef.recycle(reader); } /** * Save a {@linkplain Pyramid mosaic} via its {@linkplain PyramidalModel model}. * * @param pm The {@linkplain PyramidalModel model} of this pyramid. Must not be {@code null}. * @param pyramid {@link Pyramid} to store. Must not be {@code null}. * @param reader {@linplain GridCoverageReader reader} of the input coverage. Must not be {@code null}. * @param imageIndex Index of the image to read in the reader. * @param env {@link Envelope} of the pyramid. * @throws DataStoreException * @throws TransformException */ private void saveMosaic(final PyramidalCoverageReference pm, final Pyramid pyramid, final GridCoverageReader reader, final int imageIndex, Envelope env, boolean reduce) throws DataStoreException, TransformException, ProcessException { final GridCoverageReadParam params = new GridCoverageReadParam(); if (env != null) { params.setEnvelope(env); }else{ env = reader.getGridGeometry(imageIndex).getEnvelope(); } final CoordinateReferenceSystem crs = env.getCoordinateReferenceSystem(); GridCoverage2D coverage = (GridCoverage2D) reader.read(imageIndex, params); //straighten coverage final ParameterValueGroup subParams = StraightenDescriptor.INPUT_DESC.createValue(); Parameters.getOrCreate(StraightenDescriptor.COVERAGE_IN, subParams).setValue(coverage); final Process subprocess = StraightenDescriptor.INSTANCE.createProcess(subParams); ParameterValueGroup result; try{ result = subprocess.call(); }catch(ProcessException ex){ throw new ProcessException(ex.getMessage(), this, ex); } coverage = (GridCoverage2D) Parameters.getOrCreate(StraightenDescriptor.COVERAGE_OUT, result).getValue(); //reduce to valid domain if(reduce){ final ParameterValueGroup redParams = ReduceToDomainDescriptor.INPUT_DESC.createValue(); Parameters.getOrCreate(ReduceToDomainDescriptor.COVERAGE_IN, redParams).setValue(coverage); final Process redprocess = ReduceToDomainDescriptor.INSTANCE.createProcess(redParams); try{ result = redprocess.call(); coverage = (GridCoverage2D) Parameters.getOrCreate(StraightenDescriptor.COVERAGE_OUT, result).getValue(); }catch(ProcessException ex){ throw new ProcessException(ex.getMessage(), this, ex); } } final GridGeometry2D gridgeom = coverage.getGridGeometry(); //we know it's an affine transform since we straighten the coverage final AffineTransform2D gridToCRS = (AffineTransform2D) gridgeom.getGridToCRS2D(PixelOrientation.UPPER_LEFT); final double scale = gridToCRS.getScaleX(); final RenderedImage img = ((GridCoverage2D) coverage).getRenderedImage(); final Dimension gridSize = new Dimension(1, 1); final Dimension TileSize = new Dimension(img.getWidth(), img.getHeight()); final Envelope covEnv = coverage.getEnvelope(); final GeneralDirectPosition upperleft = new GeneralDirectPosition(crs); upperleft.setOrdinate(0, covEnv.getMinimum(0)); upperleft.setOrdinate(1, covEnv.getMaximum(1)); //envelope seems to lost its additional 2D+ values for (int i = 2, n = env.getDimension(); i < n; i++) { upperleft.setOrdinate(i, env.getMedian(i)); } final GridMosaic mosaic = pm.createMosaic(pyramid.getId(), gridSize, TileSize, upperleft, scale); pm.writeTile(pyramid.getId(), mosaic.getId(), 0, 0, img); } }