Package org.vfny.geoserver.wms.responses

Source Code of org.vfny.geoserver.wms.responses.DefaultRasterMapProducer

/* Copyright (c) 2001 - 2007 TOPP - www.openplans.org.  All rights reserved.
* This code is licensed under the GPL 2.0 license, availible at the root
* application directory.
*/
package org.vfny.geoserver.wms.responses;

import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.awt.image.IndexColorModel;
import java.awt.image.RenderedImage;
import java.io.File;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.media.jai.ImageLayout;
import javax.media.jai.Interpolation;
import javax.media.jai.InterpolationBicubic2;
import javax.media.jai.InterpolationBilinear;
import javax.media.jai.InterpolationNearest;
import javax.media.jai.JAI;
import javax.media.jai.LookupTableJAI;
import javax.media.jai.operator.LookupDescriptor;

import org.geoserver.platform.ServiceException;
import org.geoserver.wms.DefaultWebMapService;
import org.geoserver.wms.WMS;
import org.geoserver.wms.WMSInfo;
import org.geoserver.wms.WatermarkInfo;
import org.geoserver.wms.WMSInfo.WMSInterpolation;
import org.geoserver.wms.responses.MapDecoration;
import org.geoserver.wms.responses.MapDecorationLayout;
import org.geoserver.wms.responses.MetatiledMapDecorationLayout;
import org.geoserver.wms.responses.decoration.WatermarkDecoration;
import org.geotools.image.palette.InverseColorMapOp;
import org.geotools.map.MapLayer;
import org.geotools.renderer.lite.StreamingRenderer;
import org.geotools.renderer.shape.ShapefileRenderer;
import org.geotools.styling.Style;
import org.vfny.geoserver.global.GeoserverDataDirectory;
import org.vfny.geoserver.wms.RasterMapProducer;
import org.vfny.geoserver.wms.WMSMapContext;
import org.vfny.geoserver.wms.WmsException;
import org.vfny.geoserver.wms.requests.GetMapRequest;
import org.vfny.geoserver.wms.responses.map.metatile.MetatileMapProducer;

/**
* Abstract base class for GetMapProducers that relies in LiteRenderer for
* creating the raster map and then outputs it in the format they specializes
* in.
*
* <p>
* This class does the job of producing a BufferedImage using geotools
* LiteRenderer, so it should be enough for a subclass to implement
* {@linkPlain #formatImageOutputStream(String, BufferedImage, OutputStream)}
* </p>
*
* <p>
* Generates a map using the geotools jai rendering classes. Uses the Lite
* renderer, loading the data on the fly, which is quite nice. Thanks Andrea and
* Gabriel. The word is that we should eventually switch over to
* StyledMapRenderer and do some fancy stuff with caching layers, but I think we
* are a ways off with its maturity to try that yet. So Lite treats us quite
* well, as it is stateless and therefore loads up nice and fast.
* </p>
*
* <p>
* </p>
*
* @author Chris Holmes, TOPP
* @author Simone Giannecchini, GeoSolutions
* @version $Id: DefaultRasterMapProducer.java 14072 2010-03-03 12:13:14Z aaime $
*/
public abstract class DefaultRasterMapProducer extends
    AbstractRasterMapProducer implements RasterMapProducer {
    private final static Interpolation NN_INTERPOLATION = new InterpolationNearest();

    private final static Interpolation BIL_INTERPOLATION = new InterpolationBilinear();

    private final static Interpolation BIC_INTERPOLATION = new InterpolationBicubic2(
            0);

    // antialiasing settings, no antialias, only text, full antialias
    private final static String AA_NONE = "NONE";

    private final static String AA_TEXT = "TEXT";

    private final static String AA_FULL = "FULL";

    private final static List<String> AA_SETTINGS = Arrays.asList(new String[] {
            AA_NONE, AA_TEXT, AA_FULL });

    /**
     * The size of a megabyte
     */
    private static final int KB = 1024;

    /**
     * The lookup table used for data type transformation (it's really the identity one)
     */
    private static LookupTableJAI IDENTITY_TABLE = new LookupTableJAI(getTable());
    private static byte[] getTable() {
        byte[] arr = new byte[256];
        for (int i = 0; i < arr.length; i++) {
            arr[i] = (byte) i;
        }
        return arr;
    }

    /** WMS Service configuration * */
    protected WMS wms;

    /** A logger for this class. */
    private static final Logger LOGGER = org.geotools.util.logging.Logging.getLogger("org.vfny.geoserver.responses.wms.map");

    /** Which format to encode the image in if one is not supplied */
    private static final String DEFAULT_MAP_FORMAT = "image/png";

    /** The MapDecorationLayout instance **/
    private MapDecorationLayout layout;

    /** true iff this image is metatiled */
    private boolean tiled = false;


    /**
     *
     */
    public DefaultRasterMapProducer() {
        this(DEFAULT_MAP_FORMAT, null);
    }

    /**
     *
     */
    public DefaultRasterMapProducer(WMS wms) {
        this(DEFAULT_MAP_FORMAT, wms);
    }

    /**
     * @param the mime type to be written down as an HTTP header when a map of this format is generated
     */
    public DefaultRasterMapProducer(String mime, WMS wms) {
        super(mime);
        this.wms = wms;
    }

    public DefaultRasterMapProducer(String mime, String[] outputFormats, WMS wms) {
        super(mime, outputFormats);
        this.wms = wms;
    }

    /**
     * Writes the image to the client.
     *
     * @param out
     *            The output stream to write to.
     *
     * @throws org.vfny.geoserver.ServiceException
     *             DOCUMENT ME!
     * @throws java.io.IOException
     *             DOCUMENT ME!
     */
    public void writeTo(OutputStream out)
    throws ServiceException, java.io.IOException {
        formatImageOutputStream(this.image, out);
    }

    /**
     * Performs the execute request using geotools rendering.
     *
     * @param map
     *            The information on the types requested.
     *
     * @throws WmsException
     *             For any problems.
     */
    public void produceMap() throws WmsException {
        try {
            findDecorationLayout(mapContext);
        } catch (Exception e) {
            throw new WmsException(e);
        }

        Rectangle paintArea = new Rectangle(
                0, 0,
                mapContext.getMapWidth(), mapContext.getMapHeight()
        );

        if (LOGGER.isLoggable(Level.FINE)) {
            LOGGER.fine("setting up " + paintArea.width + "x" + paintArea.height + " image");
        }

        // extra antialias setting
        final GetMapRequest request = mapContext.getRequest();
        String antialias = (String) request.getFormatOptions().get("antialias");
        if (antialias != null)
            antialias = antialias.toUpperCase();

        // figure out a palette for buffered image creation
        IndexColorModel palette = null;
        final InverseColorMapOp paletteInverter = mapContext
        .getPaletteInverter();
        final boolean transparent = mapContext.isTransparent();
        final Color bgColor = mapContext.getBgColor();
        if (paletteInverter != null && AA_NONE.equals(antialias)) {
            palette = paletteInverter.getIcm();
        } else if (AA_NONE.equals(antialias)) {
            PaletteExtractor pe = new PaletteExtractor(transparent ? null : bgColor);
            MapLayer[] layers = mapContext.getLayers();
            for (int i = 0; i < layers.length; i++) {
                pe.visit(layers[i].getStyle());
                if (!pe.canComputePalette())
                    break;
            }
            if (pe.canComputePalette())
                palette = pe.getPalette();
        }

        // before even preparing the rendering surface, check it's not too big,
        // if so, throw a service exception
        long maxMemory = wms.getMaxRequestMemory() * KB;
        // ... base image memory
        long memory = getDrawingSurfaceMemoryUse(paintArea.width, paintArea.height, palette, transparent);
        // .. use a fake streaming renderer to evaluate the extra back buffers used when rendering
        // multiple featureTypeStyles against the same layer
        StreamingRenderer testRenderer = new StreamingRenderer();
        testRenderer.setContext(mapContext);
        memory += testRenderer.getMaxBackBufferMemory(paintArea.width, paintArea.height);
        if(maxMemory > 0 && memory > maxMemory) {
            long kbUsed = memory / KB;
            long kbMax = maxMemory / KB;
            throw new WmsException("Rendering request would use " + kbUsed + "KB, whilst the " +
                    "maximum memory allowed is " + kbMax + "KB");
        }

        // we use the alpha channel if the image is transparent or if the meta tiler
        // is enabled, since apparently the Crop operation inside the meta-tiler
        // generates striped images in that case (see GEOS-
        boolean useAlpha = transparent || MetatileMapProducer.isRequestTiled(request, this);
        final RenderedImage preparedImage = prepareImage(paintArea.width, paintArea.height,
                palette, useAlpha);
        final Map<RenderingHints.Key, Object> hintsMap = new HashMap<RenderingHints.Key, Object>();

        final Graphics2D graphic = ImageUtils.prepareTransparency(transparent, bgColor,
                preparedImage, hintsMap);

        // set up the antialias hints
        if (AA_NONE.equals(antialias)) {
            hintsMap.put(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_OFF);
            if (preparedImage.getColorModel() instanceof IndexColorModel) {
                // otherwise we end up with dithered colors where the match is
                // not 100%
                hintsMap.put(RenderingHints.KEY_DITHERING,
                        RenderingHints.VALUE_DITHER_DISABLE);
            }
        } else if (AA_TEXT.equals(antialias)) {
            hintsMap.put(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_OFF);
            hintsMap.put(RenderingHints.KEY_TEXT_ANTIALIASING,
                    RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        } else {
            if (antialias != null && !AA_FULL.equals(antialias)) {
                LOGGER.warning("Unrecognized antialias setting '" + antialias
                        + "', valid values are " + AA_SETTINGS);
            }
            hintsMap.put(RenderingHints.KEY_ANTIALIASING,
                    RenderingHints.VALUE_ANTIALIAS_ON);
        }


        // turn off/on interpolation rendering hint
        if(wms != null) {
            if (WMSInterpolation.Nearest.equals(wms.getInterpolation())) {
                hintsMap.put(JAI.KEY_INTERPOLATION, NN_INTERPOLATION);
                hintsMap.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR);
            } else if (WMSInterpolation.Bilinear.equals(wms.getInterpolation())) {
                hintsMap.put(JAI.KEY_INTERPOLATION, BIL_INTERPOLATION);
                hintsMap.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            } else if (WMSInterpolation.Bicubic.equals(wms.getInterpolation())) {
                hintsMap.put(JAI.KEY_INTERPOLATION, BIC_INTERPOLATION);
                hintsMap.put(RenderingHints.KEY_INTERPOLATION,RenderingHints.VALUE_INTERPOLATION_BICUBIC);
            }
        }
        // line look better with this hint, they are less blurred
        hintsMap.put(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_NORMALIZE);

        // make sure the hints are set before we start rendering the map
        graphic.setRenderingHints(hintsMap);

        RenderingHints hints = new RenderingHints(hintsMap);
        if(DefaultWebMapService.useStreamingRenderer())
            renderer = new StreamingRenderer();
        else
            renderer = new ShapefileRenderer();
        renderer.setContext(mapContext);
        renderer.setJava2DHints(hints);

        // setup the renderer hints
        Map<Object, Object> rendererParams = new HashMap<Object, Object>();
        rendererParams.put("optimizedDataLoadingEnabled", new Boolean(true));
        rendererParams.put("renderingBuffer", new Integer(mapContext
                .getBuffer()));
        rendererParams.put("maxFiltersToSendToDatastore",  DefaultWebMapService.getMaxFilterRules());
        rendererParams.put(ShapefileRenderer.SCALE_COMPUTATION_METHOD_KEY,
                ShapefileRenderer.SCALE_OGC);
        if(AA_NONE.equals(antialias)) {
            rendererParams.put(ShapefileRenderer.TEXT_RENDERING_KEY,
                    ShapefileRenderer.TEXT_RENDERING_STRING);
        } else {
            rendererParams.put(ShapefileRenderer.TEXT_RENDERING_KEY,
                    ShapefileRenderer.TEXT_RENDERING_OUTLINE);
        }
        if(DefaultWebMapService.isLineWidthOptimizationEnabled()) {
            rendererParams.put(StreamingRenderer.LINE_WIDTH_OPTIMIZATION_KEY, true);
        }
        if(DefaultWebMapService.isAdvancedProjectionHandlingEnabled()) {
            rendererParams.put(StreamingRenderer.ADVANCED_PROJECTION_HANDLING_KEY, true);
        }

        boolean kmplacemark = false;
        if (mapContext.getRequest().getFormatOptions().get("kmplacemark") != null)
            kmplacemark = ((Boolean) mapContext.getRequest().getFormatOptions()
                    .get("kmplacemark")).booleanValue();
        if (kmplacemark) {
            // create a StyleVisitor that copies a style, but removes the
            // PointSymbolizers and TextSymbolizers
            KMLStyleFilteringVisitor dupVisitor = new KMLStyleFilteringVisitor();
           
            // Remove PointSymbolizers and TextSymbolizers from the
            // layers' Styles to prevent their rendering on the
            // raster image. Both are better served with the
            // placemarks.
            MapLayer[] layers = mapContext.getLayers();
            for (int i = 0; i < layers.length; i++) {
                Style style = layers[i].getStyle();
                style.accept(dupVisitor);
                Style copy = (Style) dupVisitor.getCopy();
                layers[i].setStyle(copy);
            }
        }
        renderer.setRendererHints(rendererParams);

        // if abort already requested bail out
        if (this.abortRequested) {
            graphic.dispose();
            return;
        }

        // enforce no more than x rendering errors
        int maxErrors = wms.getMaxRenderingErrors();
        MaxErrorEnforcer errorChecker = new MaxErrorEnforcer(renderer, maxErrors);

        // Add a render listener that ignores well known rendering exceptions and reports back non
        // ignorable ones
        final RenderExceptionStrategy nonIgnorableExceptionListener;
        nonIgnorableExceptionListener = new RenderExceptionStrategy(renderer);
        renderer.addRenderListener(nonIgnorableExceptionListener);

        // setup the timeout enforcer (the enforcer is neutral when the timeout is 0)
        int maxRenderingTime = wms.getMaxRenderingTime() * 1000;
        RenderingTimeoutEnforcer timeout = new RenderingTimeoutEnforcer(maxRenderingTime, renderer,
                graphic);
        timeout.start();
        try {
            // finally render the image;
            renderer.paint(graphic, paintArea, getRenderingArea(), getRenderingTransform());

            // apply watermarking
            try {
                if (layout != null)
                    this.layout.paint(graphic, paintArea, mapContext);
            } catch (Exception e) {
                throw new WmsException("Problem occurred while trying to watermark data", "", e);
            }
        } finally {
            timeout.stop();
            graphic.dispose();
        }

        // check if the request did timeout
        if (timeout.isTimedOut()) {
            throw new WmsException(
                    "This requested used more time than allowed and has been forcefully stopped. "
                    + "Max rendering time is " + (maxRenderingTime / 1000.0) + "s");
        }

        //check if a non ignorable error occurred
        if(nonIgnorableExceptionListener.exceptionOccurred()){
            Exception renderError = nonIgnorableExceptionListener.getException();
            throw new WmsException("Rendering process failed", "internalError", renderError);
        }

        // check if too many errors occurred
        if(errorChecker.exceedsMaxErrors()) {
            throw new WmsException("More than " + maxErrors + " rendering errors occurred, bailing out.",
                    "internalError", errorChecker.getLastException());
        }

        if (!this.abortRequested) {
            if(palette != null && palette.getMapSize() < 256)
                this.image = optimizeSampleModel(preparedImage);
            else
                this.image = preparedImage;
        }
    }


    /**
     * Set the Watermark Painter.
     *
     * @param wmPainter
     *            the wmPainter to set
     */
    public void setDecorationLayout(MapDecorationLayout layout) {
        this.layout = layout;
    }

    public void findDecorationLayout(WMSMapContext mapContext) throws Exception {
        String layoutName = null;
        if (mapContext.getRequest().getFormatOptions() != null) {
            layoutName = (String)mapContext.getRequest().getFormatOptions().get("layout");
        }

        if (layoutName != null){
            try {
                File layoutDir = GeoserverDataDirectory.findConfigDir(
                        GeoserverDataDirectory.getGeoserverDataDirectory(),
                        "layouts"
                );

                if (layoutDir != null) {
                    File layoutConfig = new File(layoutDir, layoutName + ".xml");

                    if (layoutConfig.exists() && layoutConfig.canRead()) {
                        this.layout = MapDecorationLayout.fromFile(layoutConfig, tiled);
                    } else {
                        LOGGER.log(Level.WARNING, "Unknown layout requested: " + layoutName);
                    }
                } else {
                    LOGGER.log(Level.WARNING, "No layout directory defined");
                }
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Failed to load layout: " + layoutName, e);
            }
        }

        if (layout == null){
            layout = tiled
            ? new MetatiledMapDecorationLayout()
            : new MapDecorationLayout();
        }

        WMS wms = mapContext.getRequest().getWMS();

        if (wms != null){
            MapDecorationLayout.Block watermark =
                getWatermark(mapContext.getRequest().getWMS().getServiceInfo());
            if (watermark != null) layout.addBlock(watermark);
        }
    }

    public static MapDecorationLayout.Block getWatermark(WMSInfo wms) {
        WatermarkInfo watermark = (wms == null ? null : wms.getWatermark());
        if (watermark != null && watermark.isEnabled()) {
            Map<String, String> options = new HashMap<String,String>();
            options.put("url", watermark.getURL());
            options.put("opacity", Float.toString((255f - watermark.getTransparency())/ 2.55f));

            MapDecoration d = new WatermarkDecoration();
            try {
                d.loadOptions(options);
            } catch (Exception e) {
                LOGGER.log(Level.SEVERE, "Couldn't construct watermark from configuration", e);
                throw new WmsException(e);
            }

            MapDecorationLayout.Block.Position p = null;

            switch (watermark.getPosition()) {
            case TOP_LEFT:
                p = MapDecorationLayout.Block.Position.UL;
                break;
            case TOP_CENTER:
                p = MapDecorationLayout.Block.Position.UC;
                break;
            case TOP_RIGHT:
                p = MapDecorationLayout.Block.Position.UR;
                break;
            case MID_LEFT:
                p = MapDecorationLayout.Block.Position.CL;
                break;
            case MID_CENTER:
                p = MapDecorationLayout.Block.Position.CC;
                break;
            case MID_RIGHT:
                p = MapDecorationLayout.Block.Position.CR;
                break;
            case BOT_LEFT:
                p = MapDecorationLayout.Block.Position.LL;
                break;
            case BOT_CENTER:
                p = MapDecorationLayout.Block.Position.LC;
                break;
            case BOT_RIGHT:
                p = MapDecorationLayout.Block.Position.LR;
                break;
            default:
                throw new WmsException(
                        "Unknown WatermarkInfo.Position value.  Something is seriously wrong."
                );
            }

            return new MapDecorationLayout.Block(d, p, null, new Point(0,0));
        }

        return null;
    }

    /**
     * Indicate whether metatiling is activated for this map producer.
     */
    public void setMetatiled(boolean tiled) {
        this.tiled = tiled;
    }

    /**
     * Sets up a {@link BufferedImage#TYPE_4BYTE_ABGR} if the paletteInverter is
     * not provided, or a indexed image otherwise. Subclasses may override this
     * method should they need a special kind of image
     *
     * @param width
     * @param height
     * @param paletteInverter
     * @return
     */
    protected RenderedImage prepareImage(int width, int height,
            IndexColorModel palette, boolean transparent) {
        return ImageUtils.createImage(width, height, palette, transparent);
    }

    /**
     * When you override {@link #prepareImage(int, int, IndexColorModel, boolean)} remember
     * to override this one as well
     * @param width
     * @param height
     * @param palette
     * @param transparent
     * @return
     */
    protected long getDrawingSurfaceMemoryUse(int width, int height,
            IndexColorModel palette, boolean transparent) {
        return ImageUtils.getDrawingSurfaceMemoryUse(width, height, palette, transparent);
    }

    /**
     * @param originalImage
     * @return
     */
    protected RenderedImage forceIndexed8Bitmask(RenderedImage originalImage) {
        return ImageUtils.forceIndexed8Bitmask(originalImage, mapContext.getPaletteInverter());
    }

    /**
     * This takes an image with an indexed color model that uses
     * less than 256 colors and has a 8bit sample model, and transforms it to
     * one that has the optimal sample model (for example, 1bit if the palette only has 2 colors)
     * @param source
     * @return
     */
    private RenderedImage optimizeSampleModel(RenderedImage source) {
        int w = source.getWidth();
        int h = source.getHeight();
        ImageLayout layout = new ImageLayout();
        layout.setColorModel(source.getColorModel());
        layout.setSampleModel(source.getColorModel().createCompatibleSampleModel(w, h));
        // if I don't force tiling off with this setting an exception is thrown
        // when writing the image out...
        layout.setTileWidth(w);
        layout.setTileHeight(h);
        RenderingHints hints = new RenderingHints(JAI.KEY_IMAGE_LAYOUT, layout);
        return LookupDescriptor.create(source, IDENTITY_TABLE, hints);
    }

}
TOP

Related Classes of org.vfny.geoserver.wms.responses.DefaultRasterMapProducer

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.