/* * 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.storage.coverage; import java.awt.Image; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.*; import java.io.IOException; import java.util.Arrays; import java.util.EventListener; import java.util.Vector; import javax.media.jai.RasterFactory; import javax.swing.event.EventListenerList; import org.apache.sis.geometry.GeneralEnvelope; import org.apache.sis.storage.DataStoreException; import org.geotoolkit.coverage.GridSampleDimension; import org.geotoolkit.coverage.grid.GridCoverage2D; import org.geotoolkit.coverage.io.CoverageReader; import org.geotoolkit.coverage.io.CoverageStoreException; import org.geotoolkit.coverage.io.GridCoverageReadParam; import org.geotoolkit.coverage.io.GridCoverageReader; import org.geotoolkit.image.interpolation.Interpolation; import org.geotoolkit.image.interpolation.InterpolationCase; import org.geotoolkit.image.interpolation.Resample; import org.geotoolkit.image.iterator.PixelIteratorFactory; import org.geotoolkit.internal.referencing.CRSUtilities; import org.apache.sis.referencing.CRS; import org.geotoolkit.referencing.ReferencingUtilities; import org.apache.sis.referencing.operation.transform.MathTransforms; import org.apache.sis.internal.referencing.j2d.AffineTransform2D; import org.geotoolkit.image.BufferedImages; import org.geotoolkit.image.internal.ImageUtilities; import org.opengis.geometry.Envelope; import org.opengis.referencing.crs.CoordinateReferenceSystem; import org.opengis.referencing.datum.PixelInCell; import org.opengis.referencing.operation.MathTransform; import org.opengis.referencing.operation.TransformException; /** * On the fly calculated tiled image for a Coverage reference and grid definition. * * @author Johann Sorel (Geomatys) * @module */ public class CoverageReferenceRenderedImage implements RenderedImage{ private final CoverageReference ref; private final GridMosaic mosaic; private final ColorModel colorModel; private final SampleModel sampleModel; private final Envelope dataEnv; /** listener support */ private final EventListenerList listeners = new EventListenerList(); public CoverageReferenceRenderedImage(CoverageReference ref, GridMosaic mosaic) throws DataStoreException, IOException, TransformException { this.ref = ref; this.mosaic = mosaic; final GridCoverageReader reader = ref.acquireReader(); dataEnv = reader.getGridGeometry(ref.getImageIndex()).getEnvelope(); ref.recycle(reader); final RenderedImage prototype = getTileCoverage(0, 0).getRenderedImage(); colorModel = prototype.getColorModel(); sampleModel = prototype.getSampleModel(); //TODO we should do this here, but the GRIB reader do not return the same data //types on the reader and on the readed coverage. //TODO wait for the new NETCDF reader //sampleDimensions = ref.acquireReader().getSampleDimensions(ref.getImageIndex()); } /** * Tiles are generated on the fly, so we have informations on their generation * process but we don't have the tiles themselves. * * @return empty vector */ @Override public Vector<RenderedImage> getSources() { return new Vector<RenderedImage>(); } /** * A PortrayalRenderedImage does not have any properties * * @param name * @return always Image.UndefinedProperty */ @Override public Object getProperty(String name) { return Image.UndefinedProperty; } /** * A PortrayalRenderedImage does not have any properties * * @return always null */ @Override public String[] getPropertyNames() { return null; } /** * Fallback on the mosaic definition. * * @return mosaic grid size width * mosaic tile size width. */ @Override public int getWidth() { return mosaic.getGridSize().width * mosaic.getTileSize().width; } /** * Fallback on the mosaic definition. * * @return mosaic grid size width * mosaic tile size width. */ @Override public int getHeight() { return mosaic.getGridSize().height * mosaic.getTileSize().height; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getMinX() { return 0; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getMinY() { return 0; } /** * Fallback on the mosaic definition. * * @return mosaic grid size width. */ @Override public int getNumXTiles() { return mosaic.getGridSize().width; } /** * Fallback on the mosaic definition. * * @return mosaic grid size height. */ @Override public int getNumYTiles() { return mosaic.getGridSize().height; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getMinTileX() { return 0; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getMinTileY() { return 0; } /** * Fallback on the mosaic definition. * * @return mosaic tile size width. */ @Override public int getTileWidth() { return mosaic.getTileSize().width; } /** * Fallback on the mosaic definition. * * @return mosaic tile size height. */ @Override public int getTileHeight() { return mosaic.getTileSize().height; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getTileGridXOffset() { return 0; } /** * Generated tiles start at zero. * * @return 0 */ @Override public int getTileGridYOffset() { return 0; } /** * Returns the image's bounds as a <code>Rectangle</code>. * * <p> The image's bounds are defined by the values returned by * <code>getMinX()</code>, <code>getMinY()</code>, * <code>getWidth()</code>, and <code>getHeight()</code>. * A <code>Rectangle</code> is created based on these four methods. * * @return Rectangle */ public Rectangle getBounds() { return new Rectangle(getMinX(), getMinY(), getWidth(), getHeight()); } @Override public ColorModel getColorModel() { return colorModel; } @Override public SampleModel getSampleModel() { return sampleModel; } public GridCoverage2D getTileCoverage(int idx, int idy) throws CoverageStoreException, TransformException { final GridCoverageReadParam rparam = new GridCoverageReadParam(); Envelope tenv = mosaic.getEnvelope(idx, idy); final GeneralEnvelope genv = new GeneralEnvelope(tenv); genv.setRange(0, tenv.getMinimum(0) - mosaic.getScale(), tenv.getMaximum(0) + mosaic.getScale()); genv.setRange(1, tenv.getMinimum(1) - mosaic.getScale(), tenv.getMaximum(1) + mosaic.getScale()); tenv = ReferencingUtilities.transform(genv, dataEnv.getCoordinateReferenceSystem()); rparam.setEnvelope(tenv); final CoverageReader reader = ref.acquireReader(); final GridCoverage2D coverage = (GridCoverage2D) reader.read(0, rparam); ref.recycle(reader); return coverage; } @Override public Raster getTile(int idx, int idy) { try{ final GridCoverage2D coverage = getTileCoverage(idx, idy); final Envelope coverageEnvelope = coverage.getEnvelope2D(); final RenderedImage image = coverage.getRenderedImage(); final GridSampleDimension[] sampleDimensions = coverage.getSampleDimensions(); Interpolation interpolation = Interpolation.create(PixelIteratorFactory.createRowMajorIterator(image), InterpolationCase.NEIGHBOR, 2); //create an empty tile final Pyramid pyramid = mosaic.getPyramid(); final int tileWidth = getTileWidth(); final int tileHeight = getTileHeight(); final BufferedImage workTile; final int nbBand = sampleDimensions.length; final double[] fillValue = new double[nbBand]; Arrays.fill(fillValue,Double.NaN); final double res = mosaic.getScale(); if(sampleDimensions.length>0){ workTile = BufferedImages.createImage(tileWidth, tileHeight, sampleDimensions.length, CoverageUtilities.getDataType(sampleDimensions[0].getSampleDimensionType())); for(int i=0;i<nbBand;i++){ final double[] nodata = sampleDimensions[i].geophysics(true).getNoDataValues(); if(nodata!=null && nodata.length>0){ fillValue[i] = nodata[0]; } } }else{ workTile = new BufferedImage(tileWidth, tileHeight, BufferedImage.TYPE_INT_ARGB); } ImageUtilities.fill(workTile, fillValue[0]); // define tile translation from bufferedImage min pixel position to mosaic pixel position. final int minidx = idx * getTileWidth(); final int minidy = idy * getTileHeight(); final double mosULX = mosaic.getUpperLeftCorner().getOrdinate(0); final double mosULY = mosaic.getUpperLeftCorner().getOrdinate(1); CoordinateReferenceSystem destCrs2D = CRSUtilities.getCRS2D(pyramid.getCoordinateReferenceSystem()); MathTransform crsDestToCrsCoverage = CRS.findOperation(destCrs2D, coverageEnvelope.getCoordinateReferenceSystem(), null).getMathTransform(); MathTransform srcCRSToGrid = ((GridCoverage2D)coverage).getGridGeometry().getGridToCRS(PixelInCell.CELL_CENTER).inverse(); MathTransform crsDestToSrcGrid = MathTransforms.concatenate(crsDestToCrsCoverage, srcCRSToGrid); //define destination grid to CRS. final AffineTransform2D destImgToCRSDest = new AffineTransform2D(res, 0, 0, -res, mosULX + (minidx + 0.5) * res, mosULY - (minidy + 0.5) * res); final MathTransform destImgToCrsCoverage = MathTransforms.concatenate(destImgToCRSDest, crsDestToSrcGrid); try { final Resample resample = new Resample(destImgToCrsCoverage, workTile, interpolation, fillValue); resample.fillImage(); } catch (Exception ex) { throw new RuntimeException(ex); } fireTileCreated(idx,idy); return workTile.getData(); }catch(Exception ex){ ex.printStackTrace(); return null; } } @Override public Raster getData() { return getData(null); } @Override public WritableRaster getData(Rectangle region) { return copyData(region, null); } @Override public WritableRaster copyData(WritableRaster raster) { final Rectangle bounds = (raster!=null) ? raster.getBounds() : null; return copyData(bounds, raster); } public WritableRaster copyData(Rectangle region, WritableRaster dstRaster) { final Rectangle bounds = getBounds(); // image's bounds if (region == null) { region = bounds; } else if (!region.intersects(bounds)) { throw new IllegalArgumentException("Rectangle does not intersect datas."); } // Get the intersection of the region and the image bounds. final Rectangle xsect = (region == bounds) ? region : region.intersection(bounds); //create a raster of this size if(dstRaster == null){ SampleModel sampleModel = getSampleModel(); sampleModel = sampleModel.createCompatibleSampleModel(xsect.width, xsect.height); dstRaster = RasterFactory.createWritableRaster(sampleModel, new Point(0, 0)); } //calculate the first and last tiles index we will need final int startTileX = xsect.x / getTileWidth(); final int startTileY = xsect.y / getTileHeight(); final int endTileX = (xsect.x+xsect.width) / getTileWidth(); final int endTileY = (xsect.y+xsect.height) / getTileHeight(); //loop on each tile for (int j = startTileY; j <= endTileY; j++) { for (int i = startTileX; i <= endTileX; i++) { final Raster tile = getTile(i, j); dstRaster.setRect( i*getTileWidth(), j*getTileHeight(), tile); } } return dstRaster; } protected void fireTileCreated(int x, int y){ for(ProgressListener l : listeners.getListeners(ProgressListener.class)){ l.tileCreated(x, y); } } public void addProgressListener(ProgressListener listener){ listeners.add(ProgressListener.class, listener); } public void removeProgressListener(ProgressListener listener){ listeners.remove(ProgressListener.class, listener); } public static interface ProgressListener extends EventListener{ void tileCreated(int x, int y); } }