net.refractions.udig.render.internal.wms.basic.BasicWMSRenderer2.java Source code

Java tutorial

Introduction

Here is the source code for net.refractions.udig.render.internal.wms.basic.BasicWMSRenderer2.java

Source

/*
 *    uDig - User Friendly Desktop Internet GIS client
 *    http://udig.refractions.net
 *    (C) 2004, Refractions Research Inc.
 *
 *    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 net.refractions.udig.render.internal.wms.basic;

import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Handler;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.imageio.spi.ImageReaderSpi;
import javax.naming.OperationNotSupportedException;

import net.refractions.udig.catalog.util.CRSUtil;
import net.refractions.udig.project.ILayer;
import net.refractions.udig.project.IMap;
import net.refractions.udig.project.ProjectBlackboardConstants;
import net.refractions.udig.project.internal.StyleBlackboard;
import net.refractions.udig.project.internal.render.ViewportModel;
import net.refractions.udig.project.internal.render.impl.RendererImpl;
import net.refractions.udig.project.render.ICompositeRenderContext;
import net.refractions.udig.project.render.IMultiLayerRenderer;
import net.refractions.udig.project.render.IRenderContext;
import net.refractions.udig.project.render.RenderException;
import net.refractions.udig.render.wms.basic.WMSPlugin;
import net.refractions.udig.render.wms.basic.internal.Messages;
import net.refractions.udig.render.wms.basic.preferences.PreferenceConstants;
import net.refractions.udig.style.wms.WMSStyleContent;
import net.refractions.udig.ui.PlatformGIS;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.geotools.coverage.grid.GridCoverage2D;
import org.geotools.coverage.grid.GridCoverageFactory;
import org.geotools.data.Query;
import org.geotools.data.ows.Layer;
import org.geotools.data.ows.Service;
import org.geotools.data.ows.StyleImpl;
import org.geotools.data.wms.WebMapServer;
import org.geotools.data.wms.request.GetMapRequest;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.geometry.GeneralEnvelope;
import org.geotools.geometry.jts.ReferencedEnvelope;
import org.geotools.ows.ServiceException;
import org.geotools.referencing.CRS;
import org.geotools.referencing.crs.DefaultGeographicCRS;
import org.geotools.renderer.lite.RendererUtilities;
import org.geotools.renderer.lite.gridcoverage2d.GridCoverageRenderer;
import org.geotools.resources.image.ImageUtilities;
import org.geotools.styling.RasterSymbolizer;
import org.geotools.styling.Rule;
import org.geotools.styling.Style;
import org.geotools.xml.DocumentWriter;
import org.geotools.xml.filter.FilterSchema;
import org.opengis.filter.Filter;
import org.opengis.geometry.MismatchedDimensionException;
import org.opengis.referencing.FactoryException;
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.ReferenceIdentifier;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.opengis.referencing.operation.TransformException;

import com.vividsolutions.jts.geom.Coordinate;
import com.vividsolutions.jts.geom.Envelope;

/**
 * The basic renderer for a WMS Layer
 * <p>
 * </p>
 */
public class BasicWMSRenderer2 extends RendererImpl implements IMultiLayerRenderer {

    private static final String REFRESH_JOB = Messages.BasicWMSRenderer2_refreshJob_title;
    private static final String EPSG_4326 = "EPSG:4326"; //$NON-NLS-1$
    private static final String EPSG_4269 = "EPSG:4269"; //$NON-NLS-1$
    private static final ReferencedEnvelope NILL_BOX = new ReferencedEnvelope(0, 0, 0, 0,
            DefaultGeographicCRS.WGS84);
    private static final String EPSG_CODE = "CRS_EPSG_CODE";

    /**
     * Construct a new BasicWMSRenderer
     */
    public BasicWMSRenderer2() {
        ClassLoader current = Thread.currentThread().getContextClassLoader();
        try {
            Thread.currentThread().setContextClassLoader(WebMapServer.class.getClassLoader());
            Logger logger = Logger.getLogger("org.geotools.data.ows");//$NON-NLS-1$
            if (WMSPlugin.isDebugging(Trace.RENDER)) {
                logger.setLevel(Level.FINE);
                logger.addHandler(new Handler() {

                    @Override
                    public void publish(LogRecord record) {
                        System.err.println(record.getMessage());
                    }

                    @Override
                    public void flush() {
                        System.err.flush();
                    }

                    @Override
                    public void close() throws SecurityException {

                    }

                });
            } else {
                logger.setLevel(Level.SEVERE);
            }
        } finally {
            Thread.currentThread().setContextClassLoader(current);
        }
    }

    @Override
    public void render(Graphics2D destination, IProgressMonitor monitor) throws RenderException {
        render(destination, getContext().getImageBounds(), monitor);
    }

    @Override
    public void render(IProgressMonitor monitor) throws RenderException {
        Graphics2D graphics = (Graphics2D) getContext().getImage().getGraphics();
        render(graphics, getRenderBounds(), monitor);
    }

    public synchronized void render(Graphics2D destination, Envelope bounds2, IProgressMonitor monitor)
            throws RenderException {

        ReferencedEnvelope bounds = null;

        int endLayerStatus = ILayer.DONE;
        try {
            if (bounds2 == null || bounds2.isNull()) {
                bounds = getContext().getImageBounds();
            } else {
                bounds = new ReferencedEnvelope(bounds2, getViewportCRS());
            }
            if (monitor.isCanceled())
                return;

            getContext().setStatus(ILayer.WAIT);

            WebMapServer wms = getWMS();
            GetMapRequest request = wms.createGetMapRequest();
            request.setExceptions(GetMapRequest.EXCEPTION_XML);
            setImageFormat(wms, request);

            if (monitor.isCanceled())
                return;

            double currScale = getContext().getViewportModel().getScaleDenominator();
            List<ILayer> layers = getLayers();
            for (int i = layers.size() - 1; i >= 0; i--) {
                ILayer ilayer = layers.get(i);
                Layer layer;
                double minScale = 0;
                double maxScale = Double.MAX_VALUE;
                layer = ilayer.getResource(org.geotools.data.ows.Layer.class, null);
                // check if there are min/max scale rules
                StyleBlackboard sb = (StyleBlackboard) ilayer.getStyleBlackboard();
                Style style = (Style) sb.lookup(Style.class);
                if (style != null) {
                    Rule rule = style.getFeatureTypeStyles()[0].getRules()[0];
                    minScale = rule.getMinScaleDenominator();
                    maxScale = rule.getMaxScaleDenominator();
                }

                if (currScale >= minScale && currScale <= maxScale) {
                    // check for a wms style
                    StyleImpl wmsStyle = (StyleImpl) ilayer.getStyleBlackboard().get(WMSStyleContent.WMSSTYLE);
                    if (wmsStyle != null) {
                        request.addLayer(layer, wmsStyle);
                    } else {
                        request.addLayer(layer);
                    }
                }
            }

            if (monitor.isCanceled())
                return;

            List<Layer> wmsLayers = getWMSLayers();
            if (wmsLayers == null || wmsLayers.isEmpty()) {
                endLayerStatus = ILayer.WARNING;
                return;
            }

            // figure out request CRS
            String requestCRScode = findRequestCRS(wmsLayers, getViewportCRS(), getContext().getMap());
            // TODO: make findRequestCRS more efficient (we are running CRS.decode at *least* twice)
            CoordinateReferenceSystem requestCRS = CRS.decode(requestCRScode);

            // figure out viewport
            //            ReferencedEnvelope viewport;
            //            Envelope viewportBBox = getViewportBBox();
            //            CoordinateReferenceSystem viewportCRS = getViewportCRS();
            //            if (viewportBBox == null) {
            //                // change viewport to world
            //                viewportBBox = new Envelope(-180, 180, -90, 90);
            //                if (!DefaultGeographicCRS.WGS84.equals(viewportCRS)) { // reproject
            //                    viewport = new ReferencedEnvelope(viewportBBox, DefaultGeographicCRS.WGS84);
            //                    viewportBBox = viewport.transform(viewportCRS, true);
            //                }
            //            }

            ReferencedEnvelope requestBBox = null;
            Envelope backprojectedBBox = null; // request bbox projected to the viewport crs
            //            viewport = new ReferencedEnvelope(viewportBBox, viewportCRS);
            //            requestBBox = calculateRequestBBox(wmsLayers, viewport, requestCRS);
            requestBBox = calculateRequestBBox(wmsLayers, bounds, requestCRS);

            // check that a request is needed (not out of a bounds, invalid, etc)
            if (requestBBox == NILL_BOX) {
                endLayerStatus = ILayer.WARNING;
                return;
            }
            assert requestBBox.getCoordinateReferenceSystem().equals(requestCRS);

            if (requestBBox.getCoordinateReferenceSystem().equals(getViewportCRS())) {
                backprojectedBBox = (Envelope) requestBBox;
            } else {
                backprojectedBBox = (Envelope) requestBBox.transform(getViewportCRS(), true);
            }

            if (WMSPlugin.isDebugging(Trace.RENDER)) {
                WMSPlugin.trace("Viewport CRS: " + getViewportCRS().getName()); //$NON-NLS-1$
                WMSPlugin.trace("Request CRS: " + requestCRS.getName()); //$NON-NLS-1$
                WMSPlugin.trace("Context bounds: " + getContext().getImageBounds()); //$NON-NLS-1$
                WMSPlugin.trace("Request bounds: " + requestBBox); //$NON-NLS-1$
                WMSPlugin.trace("Backprojected request bounds: " + backprojectedBBox); //$NON-NLS-1$
            }

            Service wmsService = wms.getCapabilities().getService();
            Dimension maxDimensions = new Dimension(wmsService.getMaxWidth(), wmsService.getMaxHeight());
            //            Dimension imageDimensions = calculateImageDimensions(getContext().getMapDisplay()
            //                    .getDisplaySize(), maxDimensions, getViewportBBox(), backprojectedBBox);
            Dimension imageDimensions = calculateImageDimensions(getContext().getImageSize(), maxDimensions, bounds,
                    backprojectedBBox);
            if (imageDimensions.height < 1 || imageDimensions.width < 1) {
                endLayerStatus = ILayer.WARNING;
                return;
            }

            request.setDimensions(imageDimensions.width + "", imageDimensions.height + ""); //$NON-NLS-1$ //$NON-NLS-2$
            request.setBBox(requestBBox.getMinX() + "," + requestBBox.getMinY() //$NON-NLS-1$
                    + "," + requestBBox.getMaxX() //$NON-NLS-1$
                    + "," + requestBBox.getMaxY()); //$NON-NLS-1$

            // epsg could be under identifiers or authority.
            Set<ReferenceIdentifier> identifiers = requestCRS.getIdentifiers();
            if (identifiers.isEmpty())
                request.setSRS(EPSG_4326);
            else
                request.setSRS(identifiers.iterator().next().toString());

            if (monitor.isCanceled())
                return;

            setFilter(wms, request);

            // request.setProperty("DACS_ACS", null);
            BufferedImage image = readImage(wms, request, monitor);

            if (monitor.isCanceled())
                return;

            if (image == null) {
                Exception e = new RuntimeException(Messages.BasicWMSRenderer2_unable_to_decode_image);
                throw wrapException(e);
            }

            // backprojectedBBox or viewportBBox
            renderGridCoverage(destination, backprojectedBBox, imageDimensions, requestBBox, image);

        } catch (Exception e) {
            if (e instanceof RenderException)
                throw (RenderException) e;
            throw new RenderException(e);
        } finally {
            getContext().setStatus(endLayerStatus);
            if (endLayerStatus == ILayer.DONE) {
                // clear the status message (rendering was successful)
                getContext().setStatusMessage(null);
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void setFilter(WebMapServer wms, GetMapRequest request) {
        Filter mapFilter = null;

        Map<ILayer, Filter> filters = new HashMap<ILayer, Filter>();
        List<ILayer> layers = getContext().getLayers();
        for (ILayer layer : layers) {
            Object layerFilter = layer.getStyleBlackboard().get(ProjectBlackboardConstants.LAYER__DATA_QUERY);
            Filter filter;
            if (layerFilter instanceof Query) {
                filter = (Filter) ((Query) layerFilter).getFilter();
            } else if (layerFilter instanceof Filter) {
                filter = (Filter) layerFilter;
            } else {
                filter = mapFilter;
            }
            if (filter != null && filter != Filter.INCLUDE) {
                filters.put(layer, filter);
            }
        }

        if (filters.isEmpty())
            return;

        StringBuilder builder = new StringBuilder();
        HashMap hashMap = new HashMap();
        for (Map.Entry<ILayer, Filter> entry : filters.entrySet()) {
            if (entry.getValue() == null)
                builder.append('(');
            try {
                StringWriter writer = new StringWriter();
                DocumentWriter.writeDocument(entry.getValue(), FilterSchema.getInstance(), writer, hashMap);
                builder.append(writer.toString());
            } catch (OperationNotSupportedException e) {
                WMSPlugin.log("Error writing filter for layer: " + entry.getKey().getID(), e); //$NON-NLS-1$
                builder.append("<Filter/>"); //$NON-NLS-1$
            } catch (IOException e) {
                // SHOULDN'T Happen I don't think...
                assert false;
                WMSPlugin.log("Error writing filter for layer: " + entry.getKey().getID(), e); //$NON-NLS-1$
                builder.append("<Filter/>"); //$NON-NLS-1$
            }
            builder.append(')');
        }

        try {
            String encode = URLEncoder.encode(builder.toString(), "UTF-8"); //$NON-NLS-1$
            request.setVendorSpecificParameter("filter", encode); //$NON-NLS-1$
        } catch (UnsupportedEncodingException e) {
            // better not happen!
            throw (RuntimeException) new RuntimeException().initCause(e);
        }
    }

    private void renderGridCoverage(Graphics2D graphics, Envelope bounds, Dimension dimension,
            ReferencedEnvelope requestBBox, BufferedImage image) throws Exception {
        CoordinateReferenceSystem destinationCRS = getContext().getCRS();

        Envelope envelope = bounds;
        if (envelope == null || envelope.isNull()) {
            //get the bounds from the context
            envelope = getContext().getImageBounds();
        }
        Point upperLeft = getContext().worldToPixel(new Coordinate(envelope.getMinX(), envelope.getMinY()));
        Point bottomRight = getContext().worldToPixel(new Coordinate(envelope.getMaxX(), envelope.getMaxY()));
        Rectangle screenSize = new Rectangle(upperLeft);
        screenSize.add(bottomRight);

        GridCoverage2D coverage = convertImageToGridCoverage(requestBBox, image);

        AffineTransform worldToScreen = RendererUtilities.worldToScreenTransform(envelope, screenSize,
                destinationCRS);
        GridCoverageRenderer paint = new GridCoverageRenderer(destinationCRS, envelope, screenSize, worldToScreen);

        RasterSymbolizer symbolizer = CommonFactoryFinder.getStyleFactory(null).createRasterSymbolizer();

        paint.paint(graphics, coverage, symbolizer);

    }

    private GridCoverage2D convertImageToGridCoverage(ReferencedEnvelope requestBBox, BufferedImage image)
            throws RenderException {
        Envelope env = requestBBox;
        GeneralEnvelope gtEnv = new GeneralEnvelope(new double[] { env.getMinX(), env.getMinY() },
                new double[] { env.getMaxX(), env.getMaxY() });

        try {
            gtEnv.setCoordinateReferenceSystem(requestBBox.getCoordinateReferenceSystem());
        } catch (Exception e) {
            throw wrapException(e);
        }

        GridCoverageFactory factory = new GridCoverageFactory();

        GridCoverage2D gc = (GridCoverage2D) factory.create("GridCoverage", image, gtEnv); //$NON-NLS-1$
        return gc;
    }

    @SuppressWarnings("unchecked")
    private void setImageFormat(WebMapServer wms, GetMapRequest request) {
        List formats = wms.getCapabilities().getRequest().getGetMap().getFormats();
        String str;
        if (getPreferencesStore().getBoolean(PreferenceConstants.P_USE_DEFAULT_ORDER)) {
            str = getPreferencesStore().getDefaultString(PreferenceConstants.P_IMAGE_TYPE_ORDER);
        } else {
            str = getPreferencesStore().getString(PreferenceConstants.P_IMAGE_TYPE_ORDER);
        }
        String[] preferredFormats = str.split(","); //$NON-NLS-1$
        // Select one of the available formats from the WMS server
        // the order of preferred formats is set in the preferences
        for (String format : preferredFormats) {
            if (formats.contains(format)) {
                request.setProperty(GetMapRequest.FORMAT, format);
                request.setTransparent(formatSupportsTransparency(format));
                break;
            }
        }
    }

    private BufferedImage readImage(final WebMapServer wms, final GetMapRequest request, IProgressMonitor monitor)
            throws RenderException {
        final BufferedImage[] image = new BufferedImage[1];
        final RenderException[] exception = new RenderException[1];
        final Object condition = new Object();

        Thread thread = new Thread() {
            @Override
            public void run() {
                InputStream inputStream = null;
                try {
                    inputStream = wms.issueRequest(request).getInputStream();
                    image[0] = ImageIO.read(inputStream);
                } catch (IOException e1) {
                    exception[0] = wrapException(e1);
                } catch (ServiceException e) {
                    exception[0] = wrapException(e);
                } catch (Throwable t) {
                    String message = Messages.BasicWMSRenderer2_errorObtainingImage;
                    image[0] = getContext().getImage();
                    exception[0] = new RenderException(message, t);
                } finally {
                    synchronized (condition) {
                        condition.notify();
                    }
                    if (inputStream != null)
                        try {
                            inputStream.close();
                        } catch (IOException e) {
                            WMSPlugin.log("failed to close input stream!!!", e); //$NON-NLS-1$
                        }
                }
            }
        };
        thread.start();

        int time = 0;

        while (image[0] == null && !monitor.isCanceled() && exception[0] == null) {
            synchronized (condition) {
                try {
                    time += 200;
                    condition.wait(200);
                    if (time == 1000) {
                        setState(RENDERING);
                        time = 0;
                    }
                } catch (InterruptedException e) {
                    thread.interrupt();
                    throw wrapException(e);
                }
            }
        }

        if (exception[0] != null)
            throw exception[0];
        return image[0];
    }

    private boolean formatSupportsTransparency(String format) {
        if (format.equalsIgnoreCase("image/png")) //$NON-NLS-1$
            return true;
        if (format.equalsIgnoreCase("image/png8")) //$NON-NLS-1$
            return true;
        if (format.equalsIgnoreCase("image/gif")) //$NON-NLS-1$
            return true;
        if (format.equalsIgnoreCase("image/tiff")) //$NON-NLS-1$
            return true;
        if (format.equalsIgnoreCase("image/bmp")) //$NON-NLS-1$
            return true;
        return false;
    }

    /**
     * Determines the dimensions of the image to request, usually 1:1 to display, but sometimes more
     * (too fuzzy) or less (image too large) when reprojecting.
     * 
     * @param maxDimensions TODO
     * @param viewport
     * @param request
     * @param context
     * @return
     * @throws RenderException
     */
    public static Dimension calculateImageDimensions(Dimension displaySize, Dimension maxDimensions,
            Envelope viewport, Envelope request) throws RenderException {
        double xScale = request.getWidth() / viewport.getWidth();
        double yScale = request.getHeight() / viewport.getHeight();
        // TODO: adjust height and width when we are reprojecting to make things less fuzzy

        int width = (int) (xScale * displaySize.getWidth());
        int height = (int) (yScale * displaySize.getHeight());

        // ensure we don't exceed the dimensions the server will allow
        int maxWidth = (int) maxDimensions.getWidth();
        if ((maxWidth > 0) && (width > maxWidth)) {
            width = maxWidth;
        }
        int maxHeight = (int) maxDimensions.getHeight();
        if ((maxHeight > 0) && (height > maxHeight)) {
            height = maxHeight;
        }

        WMSPlugin.trace("WMS request image dimensions: " + width + ", " + height); //$NON-NLS-1$ //$NON-NLS-2$
        return new Dimension(width, height);
    }

    public Point calculateImageOffset(Point min, Point max) throws RenderException {
        return new Point(Math.min(min.x, max.x), max.y);
    }

    /**
     * Using the viewport bounds and combined wms layer extents, determines an appropriate bounding
     * box by projecting the viewport into the request CRS, intersecting the bounds, and returning
     * the result.
     * 
     * @param wmsLayers all adjacent wms layers we are requesting
     * @param viewport map editor bounds and crs
     * @param requestCRS coordinate reference system supported by the server
     * @return the bbox to ask the server for
     * @throws MismatchedDimensionException
     * @throws TransformException
     * @throws FactoryException
     */
    public static ReferencedEnvelope calculateRequestBBox(List<Layer> wmsLayers, ReferencedEnvelope viewport,
            CoordinateReferenceSystem requestCRS)
            throws MismatchedDimensionException, TransformException, FactoryException {
        /* The bounds of all wms layers on this server combined */
        Envelope layersBBox = getLayersBoundingBox(requestCRS, wmsLayers);
        if (isEnvelopeNull(layersBBox)) {
            // the wms server has no bounds
            WMSPlugin.log("Zero width/height envelope: wmsLayers = " + layersBBox); //$NON-NLS-1$
            layersBBox = null;
            // alternatively, we could impose a reprojected -180,180,-90,90
        }

        /* The viewport bounds projected to the request crs */
        ReferencedEnvelope reprojectedViewportBBox = viewport.transform(requestCRS, true);
        if (isEnvelopeNull(reprojectedViewportBBox)) {
            // viewport couldn't be reprojected
            WMSPlugin.log("Zero width/height envelope: reprojected viewport from " + viewport //$NON-NLS-1$
                    + " to " + requestCRS + " returned " + reprojectedViewportBBox); //$NON-NLS-1$ //$NON-NLS-2$
        }
        // alternative for better accuracy: new ReferencedEnvelope(JTS.transform(viewport, null,
        // CRS.findMathTransform(viewportCRS, crs, true), 4), crs);

        /* The intersection of the viewport and the combined wms layers */
        Envelope interestBBox;
        if (layersBBox == null) {
            interestBBox = reprojectedViewportBBox;
        } else {
            interestBBox = reprojectedViewportBBox.intersection(layersBBox);
        }
        if (isEnvelopeNull(interestBBox)) {
            // outside of bounds, do not draw
            WMSPlugin.trace("Bounds of the data are outside the bounds of the viewscreen."); //$NON-NLS-1$
            return NILL_BOX;
        }

        /* The bounds of the request we are going to make */
        ReferencedEnvelope requestBBox = new ReferencedEnvelope(interestBBox, requestCRS);
        return requestBBox;
    }

    private static boolean isEnvelopeNull(Envelope bbox) {
        if (bbox.getWidth() <= 0 || bbox.getHeight() <= 0) {
            return true;
        }
        return false;
    }

    // private static ReferencedEnvelope calculateOldRequestBoundingBox(
    // final boolean clientSideReprojection, final Envelope viewportBBox,
    // final CoordinateReferenceSystem viewportCRS, final List<Layer> wmsLayers,
    // final boolean[] isFullSizeOut ) throws RenderException {
    // isFullSizeOut[0] = false;
    //
    // Envelope viewportBBox2 = viewportBBox;
    // if (viewportBBox2 == null) {
    // viewportBBox2 = new Envelope(-180, 180, -90, 90);
    // }
    //
    // Envelope layersBBox = getLayersBoundingBox(viewportCRS, wmsLayers);
    // if (layersBBox == null)
    // try {
    // return new ReferencedEnvelope(viewportBBox, viewportCRS).transform(
    // DefaultGeographicCRS.WGS84, true);
    // } catch (TransformException e1) {
    // throw (RuntimeException) new RuntimeException(Messages.BasicWMSRenderer2_error)
    // .initCause(e1);
    // } catch (FactoryException e1) {
    // throw (RuntimeException) new RuntimeException(Messages.BasicWMSRenderer2_error)
    // .initCause(e1);
    // }
    //
    // if (!layersBBox.intersects(viewportBBox2))
    // return new ReferencedEnvelope(new Envelope(0, 0, 0, 0), viewportCRS);
    //
    // double minx, miny, maxx, maxy;
    // minx = layersBBox.getMinX();
    // maxx = layersBBox.getMaxX();
    // miny = layersBBox.getMinY();
    // maxy = layersBBox.getMaxY();
    // boolean noClipping = false;
    //
    // int i = 0;
    // if (viewportBBox2.getMinX() > minx || noClipping) {
    // minx = viewportBBox2.getMinX();
    // i++;
    // }
    // if (viewportBBox2.getMinY() > miny || noClipping) {
    // miny = viewportBBox2.getMinY();
    // i++;
    // }
    // if (viewportBBox2.getMaxX() < maxx || noClipping) {
    // maxx = viewportBBox2.getMaxX();
    // i++;
    // }
    // if (viewportBBox2.getMaxY() < maxy || noClipping) {
    // maxy = viewportBBox2.getMaxY();
    // i++;
    // }
    //
    // if (i == 4)
    // isFullSizeOut[0] = true;
    //
    // ReferencedEnvelope clippedBBox = new ReferencedEnvelope(
    // new Envelope(minx, maxx, miny, maxy), viewportCRS);
    //
    // if (clientSideReprojection) {
    // // Convert the clipped bounding box to the request CRS. This is the
    // // BBox to be used in the request.
    // try {
    // String code = findRequestCRS(wmsLayers);
    // if (code == null)
    // throw new RenderException(
    // "Error has occurred in the framework! There is no common CRS in layers in renderer");
    // //$NON-NLS-1$
    // CoordinateReferenceSystem crs = CRS.decode(code);
    //
    // clippedBBox = new ReferencedEnvelope(JTS.transform(clippedBBox, null, CRS
    // .findMathTransform(viewportCRS, crs, true), 4), crs);
    // } catch (NoSuchAuthorityCodeException e) {
    // WMSPlugin.log(e.getLocalizedMessage(), e);
    // return null;
    // } catch (FactoryException e) {
    // WMSPlugin.log(e.getLocalizedMessage(), e);
    // return null;
    // } catch (MismatchedDimensionException e) {
    // WMSPlugin.log(e.getLocalizedMessage(), e);
    // return null;
    // } catch (TransformException e) {
    // e.printStackTrace();
    // WMSPlugin.log(e.getLocalizedMessage(), e);
    // return null;
    // }
    // }
    //
    // return clippedBBox;
    // }

    public static Envelope getLayersBoundingBox(CoordinateReferenceSystem crs, List<Layer> layers) {
        Envelope envelope = null;

        for (Layer layer : layers) {

            GeneralEnvelope temp = layer.getEnvelope(crs);

            if (temp != null) {
                Envelope jtsTemp = new Envelope(temp.getMinimum(0), temp.getMaximum(0), temp.getMinimum(1),
                        temp.getMaximum(1));
                if (envelope == null) {
                    envelope = jtsTemp;
                } else {
                    envelope.expandToInclude(jtsTemp);
                }
            }
        }

        return envelope;
    }

    /**
     * We have made this visible so that WMSDescribeLayer (used by InfoView2)
     * can figure out how to make the *exact* same request in order
     * to a getInfo operation. We should really store the last request
     * on the layer blackboard for this intra module communication.
     * 
     * @return SRS code
     */
    public static String findRequestCRS(List<Layer> layers, CoordinateReferenceSystem viewportCRS, IMap map) {
        String requestCRS = null;

        if (layers == null || layers.isEmpty()) {
            return null;
        }

        Collection<String> viewportEPSG = extractEPSG(map, viewportCRS);
        if (viewportEPSG != null) {

            String match = matchEPSG(layers, viewportEPSG);
            if (match != null)
                return match;
        }

        if (matchEPSG(layers, EPSG_4326))
            return EPSG_4326;

        if (matchEPSG(layers, EPSG_4269)) {
            return EPSG_4269;
        }

        Layer firstLayer = layers.get(0);
        for (Object object : firstLayer.getSrs()) {
            String epsgCode = (String) object;

            try {
                // Check to see if *we* can actually use this code first.
                CRS.decode(epsgCode);
            } catch (NoSuchAuthorityCodeException e) {
                continue;
            } catch (FactoryException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }

            if (matchEPSG(layers, epsgCode)) {
                requestCRS = epsgCode;
                return requestCRS;
            }
        }

        if (requestCRS == null) {
            // Hmm. Our layers have no SRS in common - we are in an illegal state
            WMSPlugin.log(
                    "ERROR: Illegal State: Basic WMS Renderer contains layers with no common CRS. Unable to perform request."); //$NON-NLS-1$
            return null;
        }
        return requestCRS;
    }

    private static String matchEPSG(List<Layer> layers, Collection<String> epsgCodes) {
        for (String epsg : epsgCodes) {
            if (matchEPSG(layers, epsg))
                return epsg;
        }
        return null;
    }

    private static Collection<String> extractEPSG(final IMap map, final CoordinateReferenceSystem crs) {

        if (CRS.equalsIgnoreMetadata(crs, DefaultGeographicCRS.WGS84)) {
            return Collections.singleton(EPSG_4326);
        }
        final Collection<String> codes = new HashSet<String>();
        codes.addAll(CRSUtil.extractAuthorityCodes(crs));

        final String DONT_FIND = "DONT_FIND";
        boolean search = map.getBlackboard().get(EPSG_CODE) != DONT_FIND;
        if (codes.isEmpty() && search) {
            PlatformGIS.syncInDisplayThread(new Runnable() {
                public void run() {
                    Shell shell = Display.getCurrent().getActiveShell();

                    ProgressMonitorDialog dialog = new ProgressMonitorDialog(shell);
                    try {
                        dialog.run(false, true, new IRunnableWithProgress() {

                            public void run(IProgressMonitor monitor)
                                    throws InvocationTargetException, InterruptedException {
                                CoordinateReferenceSystem found = CRSUtil.findEPSGCode(crs, monitor);
                                if (found == null) {
                                    return;
                                }

                                ViewportModel model = (ViewportModel) map.getViewportModel();
                                model.eSetDeliver(false);
                                try {
                                    model.setCRS(found);
                                    codes.addAll(CRSUtil.extractAuthorityCodes(found));
                                } finally {
                                    model.eSetDeliver(true);
                                }
                            }

                        });
                    } catch (InvocationTargetException e) {
                        WMSPlugin.log("Error tracking down EPSG Code", e);
                        dontFind(map, DONT_FIND);
                    } catch (InterruptedException e) {
                        WMSPlugin.log("Error tracking down EPSG Code", e);
                        dontFind(map, DONT_FIND);
                    }
                    dontFind(map, DONT_FIND);
                }
            });
        }

        return codes;
    }

    private static void dontFind(final IMap map, final String DONT_FIND) {
        map.getBlackboard().put(EPSG_CODE, DONT_FIND);
    }

    @SuppressWarnings("unchecked")
    private static boolean matchEPSG(List<Layer> layers, String epsgCode) {
        boolean match = true;
        for (Layer layer : layers) {
            Set srs = layer.getSrs();
            if (!srs.contains(epsgCode)) {
                match = false;
                break;
            }
        }
        return match;
    }

    private List<ILayer> getLayers() {
        List<ILayer> layers = new ArrayList<ILayer>();

        ICompositeRenderContext context1 = getContext();
        IRenderContext[] contexts = context1.getContexts()
                .toArray(new IRenderContext[context1.getContexts().size()]);

        if (contexts.length == 0)
            throw new RuntimeException(Messages.BasicWMSRenderer2_no_layers_to_render);

        for (IRenderContext renderContext : contexts) {
            if (renderContext.getLayer().isVisible()) {
                layers.add(renderContext.getLayer());
            }
        }

        if (layers.isEmpty()) {
            WMSPlugin.log("WARNING: WMS Renderer contains no layers."); //$NON-NLS-1$
        }

        return layers;
    }

    private List<Layer> getWMSLayers() throws IOException {
        List<Layer> layers = new ArrayList<Layer>();

        for (ILayer iLayer : getLayers()) {
            Layer layer = iLayer.getResource(Layer.class, null);
            layers.add(layer);
        }

        return layers;
    }

    private WebMapServer getWMS() throws IOException {
        return getContext().getLayer().getResource(WebMapServer.class, null);
    }

    @Override
    public void dispose() {
        // TODO: make sure there is nothing needing disposal
    }

    Job refreshJob = new Job(REFRESH_JOB) {
        @Override
        protected IStatus run(IProgressMonitor monitor) {
            getContext().clearImage();
            try {
                render(getContext().getImage().createGraphics(), monitor);
            } catch (Throwable e) {
                WMSPlugin.log(e.getLocalizedMessage(), e);
            }
            return Status.OK_STATUS;
        }

    };

    public void refreshImage() throws RenderException {
        if (needRefresh()) {
            refreshJob.schedule();
            try {
                refreshJob.join();
            } catch (InterruptedException e) {
                // TODO Catch e
            }
        }
    }

    /**
     * Determine whether the image needs to be refreshed
     * 
     * @return Whether the image needs to be refreshed
     */
    protected boolean needRefresh() {
        return false; // TODO
    }

    public RenderException wrapException(Throwable t) {
        getContext().setStatus(ILayer.ERROR);
        RenderException renderException = new RenderException(t.getLocalizedMessage());
        renderException.initCause(t);
        return renderException;
    }

    @Override
    public ICompositeRenderContext getContext() {
        return (ICompositeRenderContext) super.getContext();
    }

    private CoordinateReferenceSystem getViewportCRS() {
        return getContext().getViewportModel().getCRS();
    }

    private IPreferenceStore getPreferencesStore() {
        return WMSPlugin.getDefault().getPreferenceStore();
    }

}