Package org.geotools.renderer.lite.gridcoverage2d

Source Code of org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer

/*
*    GeoTools - The Open Source Java GIS Toolkit
*    http://geotools.org
*
*    (C) 2004-2008, Open Source Geospatial Foundation (OSGeo)
*
*    This library is free software; you can redistribute it and/or
*    modify it under the terms of the GNU Lesser General Public
*    License as published by the Free Software Foundation;
*    version 2.1 of the License.
*
*    This library is distributed in the hope that it will be useful,
*    but WITHOUT ANY WARRANTY; without even the implied warranty of
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
*    Lesser General Public License for more details.
*/
package org.geotools.renderer.lite.gridcoverage2d;

// J2SE dependencies
import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.geom.NoninvertibleTransformException;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImagingOpException;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.media.jai.BorderExtender;
import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.PlanarImage;

import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.coverage.GridSampleDimension;
import org.geotools.coverage.TypeMap;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.coverage.grid.GridEnvelope2D;
import org.geotools.coverage.grid.GridGeometry2D;
import org.geotools.coverage.grid.ViewType;
import org.geotools.coverage.grid.io.GridCoverage2DReader;
import org.geotools.factory.Hints;
import org.geotools.geometry.Envelope2D;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.JTS;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.image.ImageWorker;
import org.geotools.referencing.CRS;
import org.geotools.referencing.operation.builder.GridToEnvelopeMapper;
import org.geotools.referencing.operation.matrix.XAffineTransform;
import org.geotools.referencing.operation.transform.AffineTransform2D;
import org.geotools.renderer.crs.ProjectionHandler;
import org.geotools.renderer.crs.ProjectionHandlerFinder;
import org.geotools.renderer.crs.WrappingProjectionHandler;
import org.geotools.resources.i18n.ErrorKeys;
import org.geotools.resources.i18n.Errors;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.styling.RasterSymbolizer;
import org.jaitools.imageutils.ImageLayout2;
import org.opengis.coverage.grid.GridCoverage;
import org.opengis.coverage.grid.GridEnvelope;
import org.opengis.geometry.BoundingBox;
import org.opengis.metadata.spatial.PixelOrientation;
import org.opengis.parameter.GeneralParameterValue;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.datum.PixelInCell;
import org.opengis.referencing.operation.MathTransform;
import org.opengis.referencing.operation.MathTransform2D;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.algorithm.match.HausdorffSimilarityMeasure;
import com.vividsolutions.jts.geom.Envelope;
import com.vividsolutions.jts.geom.Geometry;
import com.vividsolutions.jts.geom.Polygon;
import com.vividsolutions.jts.geom.util.AffineTransformation;

/**
* A helper class for rendering  {@link GridCoverage}  objects.
* @author  Simone Giannecchini, GeoSolutions SAS
* @author  Andrea Aime, GeoSolutions SAS
* @author  Alessio Fabiani, GeoSolutions SAS
*
*
* @source $URL$
* @version  $Id$
*
*/
@SuppressWarnings("deprecation")
public final class GridCoverageRenderer {

    private static final double EPS = 1e-6;
 
    /** Logger. */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger(GridCoverageRenderer.class);
   
    /** IDENTITY */
    private static final AffineTransform IDENTITY = AffineTransform2D.getTranslateInstance(0, 0);

    /**
     * This variable is use for testing purposes in order to force this
     * {@link GridCoverageRenderer} to dump images at various steps on the disk.
     */
    private static boolean DEBUG = Boolean
            .getBoolean("org.geotools.renderer.lite.gridcoverage2d.debug");

    private static String DUMP_DIRECTORY;
    static {
        if (DEBUG) {
            final File tempDir = new File(System.getProperty("user.home"),"gt-renderer");
            if (!tempDir.exists() ) {
                if(!tempDir.mkdir())
                System.out
                        .println("Unable to create debug dir, exiting application!!!");
                DEBUG=false;
                DUMP_DIRECTORY = null;
            } else
               {
                        DUMP_DIRECTORY = tempDir.getAbsolutePath();
                         System.out.println("Rendering debug dir "+DUMP_DIRECTORY);
               }
        }

    }

    /** The Display (User defined) CRS * */
    private final CoordinateReferenceSystem destinationCRS;

    /** Area we want to draw. */
    private final GeneralEnvelope destinationEnvelope;

    /** Size of the area we want to draw in pixels. */
    private final Rectangle destinationSize;

    private final AffineTransform finalGridToWorld;

    private final AffineTransform finalWorldToGrid;

    private Hints hints = new Hints();

    private Interpolation interpolation= new InterpolationNearest();
   
    /** {@link GridCoverageFactory} to be used within this {@link GridCoverageRenderer} instance.*/
    private final GridCoverageFactory gridCoverageFactory;

    private boolean wrapEnabled = true;

    private boolean advancedProjectionHandlingEnabled = true;

    /**
     * Enables/disable map wrapping (active only when rendering off a {@link GridCoverage2DReader}
     * and when advanced projection handling has been enabled too)
     */
    public void setWrapEnabled(boolean wrapEnabled) {
        this.wrapEnabled = wrapEnabled;
    }

    /**
     * Returns true if map wrapping is enabled (active only when rendering off a
     * {@link GridCoverage2DReader} and when advanced projection handling has been enabled too)
     */
    public boolean isWrapEnabled() {
        return this.wrapEnabled;
    }

    /**
     * Enables/disables advanced projection handling (read all areas needed to make up the requested
     * map, cut them to areas where reprojection makes sense, and so on). Works only when rendering
     * off a {@link GridCoverage2DReader}.
     */
    public void setAdvancedProjectionHandlingEnabled(boolean enabled) {
        this.advancedProjectionHandlingEnabled = enabled;
    }

    /**
     * Tests if advanced projection handling is enabled (read all areas needed to make up the
     * requested map, cut them to areas where reprojection makes sense, and so on). Works only when
     * rendering off a {@link GridCoverage2DReader}.
     */
    public boolean isAdvancedProjectionHandlingEnabled() {
        return this.advancedProjectionHandlingEnabled;
    }


    /**
     * Creates a new {@link GridCoverageRenderer} object.
     *
     * @param destinationCRS
     *                the CRS of the {@link GridCoverage2D} to render.
     * @param envelope
     *                delineating the area to be rendered.
     * @param screenSize
     *                at which we want to rendere the source
     *                {@link GridCoverage2D}.
     * @param worldToScreen if not <code>null</code> and if it contains a rotation,
     * this Affine Tranform is used directly to convert from world coordinates
     * to screen coordinates. Otherwise, a standard {@link GridToEnvelopeMapper}
     * is used to calculate the affine transform.
     *
     * @throws TransformException
     * @throws NoninvertibleTransformException
     */
    public GridCoverageRenderer(
            final CoordinateReferenceSystem destinationCRS,
            final Envelope envelope,
            Rectangle screenSize,
            AffineTransform worldToScreen)
            throws TransformException, NoninvertibleTransformException {

        this(destinationCRS, envelope, screenSize, worldToScreen, null);

    }
   


    /**
     * Creates a new {@link GridCoverageRenderer} object.
     *
     * @param destinationCRS
     *                the CRS of the {@link GridCoverage2D} to render.
     * @param envelope
     *                delineating the area to be rendered.
     * @param screenSize
     *                at which we want to rendere the source
     *                {@link GridCoverage2D}.
     * @param worldToScreen if not <code>null</code> and if it contains a rotation,
     * this Affine Tranform is used directly to convert from world coordinates
     * to screen coordinates. Otherwise, a standard {@link GridToEnvelopeMapper}
     * is used to calculate the affine transform.
     *
     * @param newHints
     *                {@link RenderingHints} to control this rendering process.
     * @throws TransformException
     * @throws NoninvertibleTransformException
     */
    public GridCoverageRenderer(
            final CoordinateReferenceSystem destinationCRS,
            final Envelope envelope,
            final Rectangle screenSize,
            final AffineTransform worldToScreen,
            final RenderingHints newHints) throws TransformException,
            NoninvertibleTransformException {

        // ///////////////////////////////////////////////////////////////////
        //
        // Initialize this renderer
        //
        // ///////////////////////////////////////////////////////////////////
        this.destinationSize = screenSize;
        this.destinationCRS = destinationCRS;
        if (this.destinationCRS == null) {
            throw new TransformException(Errors.format(ErrorKeys.CANT_SEPARATE_CRS_$1,
                    this.destinationCRS));
        }
        destinationEnvelope = new GeneralEnvelope(new ReferencedEnvelope(envelope,
                this.destinationCRS));
        // ///////////////////////////////////////////////////////////////////
        //
        // FINAL DRAWING DIMENSIONS AND RESOLUTION
        // I am here getting the final drawing dimensions (on the device) and
        // the resolution for this rendererbut in the CRS of the source coverage
        // since I am going to compare this info with the same info for the
        // source coverage.
        //
        // ///////////////////////////////////////////////////////////////////

        // PHUSTAD : The gridToEnvelopeMapper does not handle rotated views.
        //
        if (worldToScreen != null && XAffineTransform.getRotation(worldToScreen) != 0.0) {
            finalWorldToGrid = new AffineTransform(worldToScreen);
            finalGridToWorld = finalWorldToGrid.createInverse();
        } else {
            final GridToEnvelopeMapper gridToEnvelopeMapper = new GridToEnvelopeMapper();
            gridToEnvelopeMapper.setPixelAnchor(PixelInCell.CELL_CORNER);
            gridToEnvelopeMapper.setGridRange(new GridEnvelope2D(destinationSize));
            gridToEnvelopeMapper.setEnvelope(destinationEnvelope);
            finalGridToWorld = new AffineTransform(gridToEnvelopeMapper.createAffineTransform());
            finalWorldToGrid = finalGridToWorld.createInverse();
        }

        //
        // HINTS Management
        //
        if (newHints != null){
            this.hints.add(newHints);
        }
        // factory as needed
        this.gridCoverageFactory = CoverageFactoryFinder.getGridCoverageFactory(this.hints);
       
        // Interpolation
        if(hints.containsKey(JAI.KEY_INTERPOLATION)){
            interpolation = (Interpolation) newHints.get(JAI.KEY_INTERPOLATION);
        }else{
            hints.add(new RenderingHints(JAI.KEY_INTERPOLATION,interpolation));
        }
        if (LOGGER.isLoggable(Level.FINE)){
            LOGGER.fine("Rendering using interpolation "+interpolation);       
        }
        setInterpolationHints();
       
        // Tile Size
        if(hints.containsKey(JAI.KEY_IMAGE_LAYOUT)){
            final ImageLayout layout= (ImageLayout) hints.get(JAI.KEY_IMAGE_LAYOUT);
//            // only tiles are valid at this stage?? TODO
            layout.unsetImageBounds();
            layout.unsetValid(ImageLayout.COLOR_MODEL_MASK&ImageLayout.SAMPLE_MODEL_MASK);
        }
        // this prevents users from overriding lenient hint
        this.hints.put(Hints.LENIENT_DATUM_SHIFT, Boolean.TRUE);
        this.hints.put(Hints.COVERAGE_PROCESSING_VIEW, ViewType.SAME);
       
        //SG add hints for the border extender
        this.hints.add(new RenderingHints(JAI.KEY_BORDER_EXTENDER,BorderExtender.createInstance(BorderExtender.BORDER_COPY)));
    }



    /**
     *
     */
    private void setInterpolationHints() {
        if(interpolation instanceof InterpolationNearest){
            this.hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.FALSE));
            this.hints.add(new RenderingHints(JAI.KEY_TRANSFORM_ON_COLORMAP, Boolean.TRUE));
        } else {
            this.hints.add(new RenderingHints(JAI.KEY_REPLACE_INDEX_COLOR_MODEL, Boolean.TRUE));
            this.hints.add(new RenderingHints(JAI.KEY_TRANSFORM_ON_COLORMAP, Boolean.FALSE));
        }
    }

    /**
     * Write the provided {@link RenderedImage} in the debug directory with the provided file name.
     *
     * @param raster
     *            the {@link RenderedImage} that we have to write.
     * @param fileName
     *            a {@link String} indicating where we should write it.
     */
    static void writeRenderedImage(final RenderedImage raster, final String fileName) {
        if (DUMP_DIRECTORY == null)
            throw new NullPointerException(
                    "Unable to write the provided coverage in the debug directory");
        if (DEBUG == false)
            throw new IllegalStateException(
                    "Unable to write the provided coverage since we are not in debug mode");
        try {
            ImageIO.write(raster, "tiff", new File(DUMP_DIRECTORY, fileName + ".tiff"));
        } catch (IOException e) {
            LOGGER.log(Level.SEVERE, e.getLocalizedMessage(), e);
        }
    }

    /**
     * Turns the coverage into a rendered image applying the necessary transformations and the
     * symbolizer
     *
     * Builds a (RenderedImage, AffineTransform) pair that can be used for rendering onto a
     * {@link Graphics2D} or as the basis to build a final image. Will return null if there is
     * nothing to render.
     *
     * @param gridCoverage
     * @param symbolizer
     * @return The transformed image, or null if the coverage does not lie within the rendering
     *         bounds
     * @throws Exception
     */
    public RenderedImage renderImage(
            final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer,
            final double[] bkgValues )throws Exception {

        final GridCoverage2D symbolizerGC = renderCoverage(gridCoverage, symbolizer, bkgValues);
        return symbolizerGC.getRenderedImage();
    }



    private GridCoverage2D renderCoverage(final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer, final double[] bkgValues) throws FactoryException {
        // Initial checks
        GridCoverageRendererUtilities.ensureNotNull(gridCoverage, "gridCoverage");     
        if (LOGGER.isLoggable(Level.FINE)){
            LOGGER.fine("Drawing coverage "+gridCoverage.toString());
        }

        // //
        //
        // REPROJECTION NEEDED?
        //
        // //
        boolean doReprojection =false;
        final CoordinateReferenceSystem coverageCRS = gridCoverage.getCoordinateReferenceSystem2D();
        if(!CRS.equalsIgnoreMetadata(coverageCRS, destinationCRS)){
            final MathTransform transform = CRS.findMathTransform(coverageCRS, destinationCRS, true);
            doReprojection = !transform.isIdentity();
            if (LOGGER.isLoggable(Level.FINE)){
                LOGGER.fine("Reproject needed for rendering provided coverage");
            }   
        }

        // /////////////////////////////////////////////////////////////////////
        //
        // CROP
        //
        // /////////////////////////////////////////////////////////////////////
        final GridCoverage2D preReprojection = crop(gridCoverage, destinationEnvelope,
                doReprojection);
        if (preReprojection == null) {
            // nothing to render, the AOI does not overlap
            if (LOGGER.isLoggable(Level.FINE)) {
                LOGGER.fine("Skipping current coverage because crop failed");
            }
            return null;
        }
       
        // /////////////////////////////////////////////////////////////////////
        //
        // REPROJECTION if needed
        //
        // /////////////////////////////////////////////////////////////////////
        final GridCoverage2D afterReprojection=reproject(preReprojection, doReprojection,bkgValues);
       
        // ///////////////////////////////////////////////////////////////////
        //
        // FINAL AFFINE
        //
        // ///////////////////////////////////////////////////////////////////
        final GridCoverage2D preSymbolizer= affine(afterReprojection,bkgValues);
        if (preSymbolizer == null) {
            return null;
        }
       
        // ///////////////////////////////////////////////////////////////////
        //
        // RASTERSYMBOLIZER
        //
        // ///////////////////////////////////////////////////////////////////
        final GridCoverage2D symbolizerGC;
        if(symbolizer!=null){
                if (LOGGER.isLoggable(Level.FINE)){
                    LOGGER.fine("Applying Raster Symbolizer ");
                }           
          final RasterSymbolizerHelper rsp = new RasterSymbolizerHelper (preSymbolizer,this.hints);
          rsp.visit(symbolizer);
          symbolizerGC = (GridCoverage2D) rsp.getOutput();
      } else {
            symbolizerGC = preSymbolizer;
        }
        if (DEBUG) {
            writeRenderedImage( symbolizerGC.geophysics(false).getRenderedImage(),"postSymbolizer");
        }
        return symbolizerGC;
    }             

    /**
     * @param preResample
     * @param doReprojection
     * @param bkgValues
     * @return
     * @throws FactoryException
     */
    private GridCoverage2D reproject(GridCoverage2D preResample, boolean doReprojection, double[] bkgValues) throws FactoryException {
        GridCoverage2D afterReprojection=null;
        try{
            if (doReprojection) {
                afterReprojection = GridCoverageRendererUtilities.reproject(
                        preResample,
                        destinationCRS,
                        interpolation,
                        destinationEnvelope,
                        bkgValues,
                        hints);
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.fine("Reprojecting to crs " + destinationCRS.toString());
            } else{
                afterReprojection = preResample;
            }
            return afterReprojection;
        }finally{
            if (DEBUG) {
                if(afterReprojection!=null){
                    writeRenderedImage(
                            afterReprojection.geophysics(false).getRenderedImage(),
                            "afterReprojection");
                }
            }
        }
    }



    /**
     * @param destinationEnvelope
     * @param gridCoverage
     * @return
     */
    private GridCoverage2D crop(
            final GridCoverage2D inputCoverage,
            final GeneralEnvelope destinationEnvelope,
            final boolean doReprojection) {
       
        // //
        //
        // CREATING THE CROP ENVELOPE
        //
        // // 
        GridCoverage2D outputCoverage=inputCoverage;
        final GeneralEnvelope coverageEnvelope = (GeneralEnvelope) inputCoverage.getEnvelope();      
        final CoordinateReferenceSystem coverageCRS = inputCoverage.getCoordinateReferenceSystem2D();

        try{
            GeneralEnvelope renderingEnvelopeInCoverageCRS=null;
            if(doReprojection){
                renderingEnvelopeInCoverageCRS = GridCoverageRendererUtilities
                        .reprojectEnvelopeWithWGS84Pivot(destinationEnvelope, coverageCRS);
            }else{
                // NO REPROJECTION
                renderingEnvelopeInCoverageCRS = new GeneralEnvelope(destinationEnvelope);
            }
            final GeneralEnvelope cropEnvelope = new GeneralEnvelope(renderingEnvelopeInCoverageCRS);
            cropEnvelope.intersect(coverageEnvelope);
            if (cropEnvelope.isEmpty()||cropEnvelope.isNull()) {
                if (LOGGER.isLoggable(Level.INFO)) {
                    LOGGER.info("The destination envelope does not intersect the envelope of the source coverage.");
                }
                return null;
            }  
           
            ////
            //
            // Cropping for real
            //
            /////
            outputCoverage = GridCoverageRendererUtilities.crop(inputCoverage, cropEnvelope, hints);
        }catch (Throwable t) {
                ////
                //
                // If it happens that the crop fails we try to proceed since the crop does only an optimization. Things might
                // work out anyway.
                //
                ////{
            if (LOGGER.isLoggable(Level.FINE)){
                LOGGER.log(Level.FINE,"Crop Failed for reason: "+t.getLocalizedMessage(),t);
            }
            outputCoverage=inputCoverage;
        }

        if (DEBUG) {
            writeRenderedImage(
                    outputCoverage.geophysics(false).getRenderedImage(),
                    "crop");
        }
        return outputCoverage;
       
    }



    /**
     * @param bkgValues
     * @param preResample
     * @return
     */
    private GridCoverage2D affine(GridCoverage2D input, double[] bkgValues) {
     // NOTICE that at this stage the image we get should be 8 bits, either RGB, RGBA, Gray, GrayA
        // either multiband or indexed. It could also be 16 bits indexed!!!!
        final RenderedImage finalImage = input.getRenderedImage();
        final GridGeometry2D preSymbolizerGridGeometry = (input.getGridGeometry());
        // I need to translate half of a pixel since in wms the envelope
        // map to the corners of the raster space not to the center of the
        // pixels.
        final MathTransform2D finalGCTransform=preSymbolizerGridGeometry.getGridToCRS2D(PixelOrientation.UPPER_LEFT);
        if (!(finalGCTransform instanceof AffineTransform)) {
            throw new UnsupportedOperationException(
                    "Non-affine transformations not yet implemented"); // TODO
        }
        final AffineTransform finalGCgridToWorld = new AffineTransform((AffineTransform) finalGCTransform);


        // //
        //
        // I am going to concatenate the final world to grid transform for the
        // screen area with the grid to world transform of the input coverage.
        //
        // This way i right away position the coverage at the right place in the
        // area of interest for the device.
        //
        // //
        final AffineTransform finalRasterTransformation = (AffineTransform) finalWorldToGrid.clone();
        finalRasterTransformation.concatenate(finalGCgridToWorld);
      
        //paranoiac check to avoid that JAI freaks out when computing its internal layouT on images that are too small
        Rectangle2D finalLayout= GridCoverageRendererUtilities.layoutHelper(
                        finalImage,
                        (float)finalRasterTransformation.getScaleX(),
                        (float)finalRasterTransformation.getScaleY(),
                        (float)finalRasterTransformation.getTranslateX(),
                        (float)finalRasterTransformation.getTranslateY(),
                        interpolation);
        if(finalLayout.isEmpty()){
                if(LOGGER.isLoggable(java.util.logging.Level.FINE))
                        LOGGER.fine("Unable to create a granuleDescriptor "+this.toString()+ " due to jai scale bug");
                return null;
        }

        RenderedImage im=null;
        try {
            ImageWorker iw = new ImageWorker(finalImage);
            iw.setRenderingHints(hints);
            iw.affine(finalRasterTransformation, interpolation, bkgValues);
            im = iw.getRenderedImage();
        } finally {
                if(DEBUG){
                    writeRenderedImage(im, "postAffine");
                }
        }       
        // recreate gridCoverage
        int numBands = im.getSampleModel().getNumBands();
        GridSampleDimension[] sd = new GridSampleDimension[numBands];
        for(int i=0;i<numBands;i++) {
            sd[i]= new GridSampleDimension(TypeMap.getColorInterpretation(im.getColorModel(), i).name());
        }
       
        // create a new grid coverage but preserve as much input as possible
       return this.gridCoverageFactory.create(
               input.getName(),
                im,
                new GridGeometry2D(
                        new GridEnvelope2D(PlanarImage.wrapRenderedImage(im).getBounds()),
                        input.getEnvelope()),
                sd,
                new GridCoverage[] { input },
                input.getProperties());   
    }



    /**
     * Turns the coverage into a rendered image applying the necessary transformations and the
     * symbolizer
     *
     * @param gridCoverage
     * @param symbolizer
     * @return The transformed image, or null if the coverage does not lie within the rendering
     *         bounds
     * @throws FactoryException
     * @throws TransformException
     * @throws NoninvertibleTransformException
     * @Deprecated
     */
    public RenderedImage renderImage(
            final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer,
            final Interpolation interpolation,
            final Color background,
            final int tileSizeX,
            final int tileSizeY
            ) throws FactoryException, TransformException, NoninvertibleTransformException {

        GridCoverage2D coverage = renderCoverage(gridCoverage, symbolizer, interpolation,
                background, tileSizeX,
                tileSizeY);
        return coverage.getRenderedImage();
    }

    private GridCoverage2D renderCoverage(final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer, final Interpolation interpolation,
            final Color background, final int tileSizeX, final int tileSizeY)
            throws FactoryException {
        final Hints oldHints= this.hints.clone();
       
        setupTilingHints(tileSizeX, tileSizeY);
        setupInterpolationHints(interpolation);
        try {
            return renderCoverage(gridCoverage, symbolizer,
                    GridCoverageRendererUtilities.colorToArray(background));
        } catch (Exception e) {
            throw new FactoryException(e);
        } finally {
            this.hints = oldHints;
        }
    }



    private void setupTilingHints(final int tileSizeX, final int tileSizeY) {
        ////
        //
        // TILING
        //
        ////
        if(tileSizeX>0&&tileSizeY>0){
            // Tile Size
            final ImageLayout layout= new ImageLayout2();
            layout.setTileGridXOffset(0).setTileGridYOffset(0).setTileHeight(tileSizeY).setTileWidth(tileSizeX);
            hints.add(new RenderingHints(JAI.KEY_IMAGE_LAYOUT,layout));           
        }
    }



    private void setupInterpolationHints(final Interpolation interpolation) {
        ////
        //
        // INTERPOLATION
        //
        ////
        if(interpolation!=null){
            if (LOGGER.isLoggable(Level.FINE)){
                LOGGER.fine("Rendering using interpolation "+interpolation);       
            }
            this.interpolation=interpolation;
            hints.add(new RenderingHints(JAI.KEY_INTERPOLATION,this.interpolation));
            if (LOGGER.isLoggable(Level.FINE)){
                LOGGER.fine("Rendering using interpolation "+interpolation);       
            }
            setInterpolationHints();
        }
    }
   
    public RenderedImage renderImage(final GridCoverage2DReader reader,
            GeneralParameterValue[] readParams, final RasterSymbolizer symbolizer, final Interpolation interpolation,
            final Color background, final int tileSizeX, final int tileSizeY) throws FactoryException, TransformException,
            NoninvertibleTransformException, IOException {
        // setup the hints
        setupTilingHints(tileSizeX, tileSizeY);
        setupInterpolationHints(interpolation);

        return renderImage(reader, readParams, symbolizer, interpolation, background);
    }

    private RenderedImage renderImage(final GridCoverage2DReader reader,
            GeneralParameterValue[] readParams, final RasterSymbolizer symbolizer,
            final Interpolation interpolation, final Color background) throws FactoryException,
            IOException, TransformException {
        // see if we have a projection handler
        CoordinateReferenceSystem sourceCRS = reader.getCoordinateReferenceSystem();
        CoordinateReferenceSystem targetCRS = destinationEnvelope.getCoordinateReferenceSystem();

        ProjectionHandler handler = null;
        List<GridCoverage2D> coverages;
        // read all the coverages we need, cut and whatnot
        GridCoverageReaderHelper rh = new GridCoverageReaderHelper(reader, destinationSize,
                ReferencedEnvelope.reference(destinationEnvelope), interpolation);
        // are we dealing with a remote service wrapped in a reader, one that can handle reprojection
        // by itself?
        if(GridCoverageReaderHelper.isReprojectingReader(reader)) {
            GridCoverage2D coverage = rh.readCoverage(readParams);
            coverages = new ArrayList<>();
            coverages.add(coverage);
        } else {
            handler = ProjectionHandlerFinder.getHandler(rh.getReadEnvelope(),
                    sourceCRS, wrapEnabled);
            if (handler instanceof WrappingProjectionHandler) {
                // raster data is monolithic and can cover the whole world, disable
                // the geometry wrapping heuristic
                ((WrappingProjectionHandler) handler).setDatelineWrappingCheckEnabled(false);
            }
            coverages = rh.readCoverages(readParams, handler);
        }

        // reproject if needed
        double[] bgValues = GridCoverageRendererUtilities.colorToArray(background);
        List<GridCoverage2D> reprojectedCoverages = new ArrayList<GridCoverage2D>();
        for (GridCoverage2D coverage : coverages) {
            final CoordinateReferenceSystem coverageCRS = coverage.getCoordinateReferenceSystem();
            if (!CRS.equalsIgnoreMetadata(coverageCRS, destinationCRS)) {
                GridCoverage2D reprojected = reproject(coverage, true, bgValues);
                if (reprojected != null) {
                    reprojectedCoverages.add(reprojected);
                }
            } else {
                reprojectedCoverages.addAll(coverages);
            }
        }

        // displace them if needed via a projection handler
        List<GridCoverage2D> displacedCoverages = new ArrayList<GridCoverage2D>();
        if (handler != null) {
            Envelope testEnvelope = ReferencedEnvelope.reference(destinationEnvelope);
            MathTransform mt = CRS.findMathTransform(sourceCRS, targetCRS);
            PolygonExtractor polygonExtractor = new PolygonExtractor();
            for (GridCoverage2D coverage : reprojectedCoverages) {
                Polygon polygon = JTS.toGeometry((BoundingBox) coverage.getEnvelope2D());
                Geometry postProcessed = handler.postProcess(mt, polygon);
                // extract sub-polygons and displace
                List<Polygon> polygons = polygonExtractor.getPolygons(postProcessed);
                for (Polygon displaced : polygons) {
                    // check we are really inside the display area before moving one
                    Envelope intersection = testEnvelope.intersection(displaced
                            .getEnvelopeInternal());
                    if (intersection == null || intersection.isNull()
                            || intersection.getArea() == 0) {
                        continue;
                    }
                    if (displaced.equals(polygon)) {
                        displacedCoverages.add(coverage);
                    } else {
                        double[] tx = getTranslationFactors(polygon, displaced);
                        if (tx != null) {
                            GridCoverage2D displacedCoverage = displaceCoverage(coverage, tx[0],
                                    tx[1]);
                            displacedCoverages.add(displacedCoverage);
                        }
                    }
                }
            }
        } else {
            displacedCoverages.addAll(reprojectedCoverages);
        }

        // if more than one coverage, mosaic
        GridCoverage2D mosaicked = null;
        if (displacedCoverages.size() == 0) {
            return null;
        } else if (displacedCoverages.size() == 1) {
            mosaicked = displacedCoverages.get(0);
        } else {
            mosaicked = GridCoverageRendererUtilities.mosaic(displacedCoverages,
                    destinationEnvelope, hints);
        }

        // the mosaicking can cut off images that are just slightly out of the
        // request (effect of the read buffer + a request touching the actual data area)
        if (mosaicked == null) {
            return null;
        }

        // render it
        GridCoverage2D result = renderCoverage(mosaicked, symbolizer,
                GridCoverageRendererUtilities.colorToArray(background));

        if (result == null) {
            return null;
        } else {
            // RenderedImageBrowser.showChain(result.getRenderedImage());
            return result.getRenderedImage();
        }
    }

    private GridCoverage2D displaceCoverage(GridCoverage2D coverage, double tx, double ty) {
        // let's compute the new grid geometry
        GridGeometry2D originalGG = coverage.getGridGeometry();
        GridEnvelope gridRange = originalGG.getGridRange();
        Envelope2D envelope = originalGG.getEnvelope2D();
       
        double minx = envelope.getMinX() + tx;
        double miny = envelope.getMinY() + ty;
        double maxx = envelope.getMaxX() + tx;
        double maxy = envelope.getMaxY() + ty;
        ReferencedEnvelope translatedEnvelope = new ReferencedEnvelope(minx, maxx, miny, maxy,
                envelope.getCoordinateReferenceSystem());

        GridGeometry2D translatedGG = new GridGeometry2D(gridRange, translatedEnvelope);

        GridCoverage2D translatedCoverage = gridCoverageFactory.create(coverage.getName(),
                coverage.getRenderedImage(), translatedGG, coverage.getSampleDimensions(),
                new GridCoverage2D[] { coverage }, coverage.getProperties());
        return translatedCoverage;
    }

    private double[] getTranslationFactors(Polygon reference, Polygon displaced) {
        // compare the two envelopes
        Envelope re = reference.getEnvelopeInternal();
        Envelope de = displaced.getEnvelopeInternal();
        double dw = Math.abs(re.getWidth() - de.getWidth());
        double dh = Math.abs(re.getHeight() - de.getHeight());
        if (dw > EPS * re.getWidth() || dh > EPS * re.getWidth()) {
            // this was not just a translation
            return null;
        }

        // compute the translation
        double dx = de.getMinX() - re.getMinX();
        double dy = de.getMinY() - re.getMinY();

        Polygon cloned = (Polygon) displaced.clone();
        cloned.apply(AffineTransformation.translationInstance(-dx, -dy));
        if (1 - new HausdorffSimilarityMeasure().measure(cloned, reference) > EPS) {
            return null;
        } else {
            return new double[] { dx, dy };
        }
    }

    /**
     * Paint this grid coverage. The caller must ensure that
     * <code>graphics</code> has an affine transform mapping "real world"
     * coordinates in the coordinate system given by {@link
     * #getCoordinateSystem}.
     *
     * @param graphics
     *                the {@link Graphics2D} context in which to paint.
     * @param metaBufferedEnvelope
     * @throws Exception
     * @throws UnsupportedOperationException
     *                 if the transformation from grid to coordinate system in
     *                 the GridCoverage is not an AffineTransform
     */
    public void paint(
            final Graphics2D graphics,
            final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer)
            throws Exception {
        paint(graphics, gridCoverage, symbolizer,null);
    }
   
    /**
     * Paint this grid coverage. The caller must ensure that
     * <code>graphics</code> has an affine transform mapping "real world"
     * coordinates in the coordinate system given by {@link
     * #getCoordinateSystem}.
     *
     * @param graphics
     *                the {@link Graphics2D} context in which to paint.
     * @param metaBufferedEnvelope
     * @throws Exception
     * @throws UnsupportedOperationException
     *                 if the transformation from grid to coordinate system in
     *                 the GridCoverage is not an AffineTransform
     */
    public void paint(
            final Graphics2D graphics,
            final GridCoverage2D gridCoverage,
            final RasterSymbolizer symbolizer,
            final double[] bkgValues)
            throws Exception {

        //
        // Initial checks
        //
        if(graphics==null){
            throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"graphics"));
        }
        if(gridCoverage==null){
            throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,"gridCoverage"));
        }
       
        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine(new StringBuilder("Drawing coverage ").append(gridCoverage.toString()).toString());

        // Build the final image and the transformation
        RenderedImage finalImage = renderImage(gridCoverage, symbolizer, bkgValues);
        paintImage(graphics, finalImage);
    }

    /**
     * Paint the coverage read from the reader (using advanced projection handling). The caller must
     * ensure that <code>graphics</code> has an affine transform mapping "real world" coordinates in
     * the coordinate system given by {@link #getCoordinateSystem}.
     *
     * @param graphics the {@link Graphics2D} context in which to paint.
     * @param metaBufferedEnvelope
     * @throws Exception
     * @throws UnsupportedOperationException if the transformation from grid to coordinate system in
     *         the GridCoverage is not an AffineTransform
     */
    public void paint(final Graphics2D graphics, final GridCoverage2DReader gridCoverageReader,
            GeneralParameterValue[] readParams, final RasterSymbolizer symbolizer,
            Interpolation interpolation, final Color background) throws Exception {

        //
        // Initial checks
        //
        if (graphics == null) {
            throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1, "graphics"));
        }
        if (gridCoverageReader == null) {
            throw new NullPointerException(Errors.format(ErrorKeys.NULL_ARGUMENT_$1,
                    "gridCoverageReader"));
        }

        if (LOGGER.isLoggable(Level.FINE))
            LOGGER.fine(new StringBuilder("Drawing reader ").append(gridCoverageReader.toString())
                    .toString());

        // Build the final image and the transformation
        RenderedImage finalImage = renderImage(gridCoverageReader, readParams, symbolizer,
                interpolation, background);
        if (finalImage != null) {
            try {
                paintImage(graphics, finalImage);
            } finally {
                if (finalImage instanceof PlanarImage) {
                    ImageUtilities.disposePlanarImageChain((PlanarImage) finalImage);
                }
            }
        }
    }

    private void paintImage(final Graphics2D graphics, RenderedImage finalImage) {
        final RenderingHints oldHints = graphics.getRenderingHints();
        graphics.setRenderingHints(this.hints);

        try {
            // debug
            if (DEBUG) {
                writeRenderedImage(finalImage, "final");
            }

            // force solid alpha, the transparency has already been
            // dealt with in the image preparation, and we have to make
            // sure previous vector rendering code did not leave a non solid alpha
            graphics.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER));

            // //
            // Drawing the Image
            // //
            graphics.drawRenderedImage(finalImage, GridCoverageRenderer.IDENTITY);

        } catch (Throwable t) {
            try {
                // log the error
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.log(Level.FINE, t.getLocalizedMessage(), t);

                // /////////////////////////////////////////////////////////////
                // this is a workaround for a bug in Java2D, we need to convert
                // the image to component color model to make it work just fine.
                // /////////////////////////////////////////////////////////////
                if (t instanceof IllegalArgumentException) {
                    if (DEBUG) {
                        writeRenderedImage(finalImage, "preWORKAROUND1");
                    }
                    final RenderedImage image = new ImageWorker(finalImage)
                            .forceComponentColorModel(true).getRenderedImage();

                    if (DEBUG) {
                        writeRenderedImage(image, "WORKAROUND1");

                    }
                    graphics.drawRenderedImage(image, GridCoverageRenderer.IDENTITY);

                } else if (t instanceof ImagingOpException)
                // /////////////////////////////////////////////////////////////
                // this is a workaround for a bug in Java2D
                // (see bug 4723021
                // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4723021).
                //
                // AffineTransformOp.filter throws a
                // java.awt.image.ImagingOpException: Unable to tranform src
                // image when a PixelInterleavedSampleModel is used.
                //
                // CUSTOMER WORKAROUND :
                // draw the BufferedImage into a buffered image of type ARGB
                // then perform the affine transform. THIS OPERATION WASTES
                // RESOURCES BY PERFORMING AN ALLOCATION OF MEMORY AND A COPY ON
                // LARGE IMAGES.
                // /////////////////////////////////////////////////////////////
                {
                    BufferedImage buf = finalImage.getColorModel().hasAlpha() ? new BufferedImage(
                            finalImage.getWidth(), finalImage.getHeight(),
                            BufferedImage.TYPE_4BYTE_ABGR) : new BufferedImage(
                            finalImage.getWidth(), finalImage.getHeight(),
                            BufferedImage.TYPE_3BYTE_BGR);
                    if (DEBUG) {
                        writeRenderedImage(buf, "preWORKAROUND2");
                    }
                    final Graphics2D g = (Graphics2D) buf.getGraphics();
                    final int translationX = finalImage.getMinX(), translationY = finalImage
                            .getMinY();
                    g.drawRenderedImage(finalImage,
                            AffineTransform.getTranslateInstance(-translationX, -translationY));
                    g.dispose();
                    if (DEBUG) {
                        writeRenderedImage(buf, "WORKAROUND2");
                    }
                    GridCoverageRenderer.IDENTITY.concatenate(AffineTransform.getTranslateInstance(
                            translationX, translationY));
                    graphics.drawImage(buf, GridCoverageRenderer.IDENTITY, null);
                    // release
                    buf.flush();
                    buf = null;
                } else
                // log the error
                if (LOGGER.isLoggable(Level.FINE))
                    LOGGER.fine("Unable to renderer this raster, no workaround found");

            } catch (Throwable t1) {
                // if the workaround fails again, there is really nothing to do
                // :-(
                LOGGER.log(Level.WARNING, t1.getLocalizedMessage(), t1);
            } finally {
                // ///////////////////////////////////////////////////////////////////
                //
                // Restore old hints
                //
                // ///////////////////////////////////////////////////////////////////
                graphics.setRenderingHints(oldHints);
            }
        }
    }

}
TOP

Related Classes of org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.