org.deegree.services.wps.provider.jrxml.contentprovider.map.MapContentProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.deegree.services.wps.provider.jrxml.contentprovider.map.MapContentProvider.java

Source

//$HeadURL: svn+ssh://lbuesching@svn.wald.intevation.de/deegree/base/trunk/resources/eclipse/files_template.xml $
/*----------------------------------------------------------------------------
 This file is part of deegree, http://deegree.org/
 Copyright (C) 2001-2010 by:
 - Department of Geography, University of Bonn -
 and
 - lat/lon GmbH -
    
 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; either version 2.1 of the License, or (at your option)
 any later version.
 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.
 You should have received a copy of the GNU Lesser General Public License
 along with this library; if not, write to the Free Software Foundation, Inc.,
 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    
 Contact information:
    
 lat/lon GmbH
 Aennchenstr. 19, 53177 Bonn
 Germany
 http://lat-lon.de/
    
 Department of Geography, University of Bonn
 Prof. Dr. Klaus Greve
 Postfach 1147, 53001 Bonn
 Germany
 http://www.geographie.uni-bonn.de/deegree/
    
 e-mail: info@deegree.org
 ----------------------------------------------------------------------------*/
package org.deegree.services.wps.provider.jrxml.contentprovider.map;

import static org.deegree.services.wps.provider.jrxml.JrxmlUtils.JASPERREPORTS_NS;
import static org.deegree.services.wps.provider.jrxml.JrxmlUtils.nsContext;
import static org.deegree.services.wps.provider.jrxml.contentprovider.map.RenderUtils.adjustSpan;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.MalformedURLException;
import java.text.DecimalFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.commons.io.IOUtils;
import org.deegree.commons.tom.ows.CodeType;
import org.deegree.commons.utils.MapUtils;
import org.deegree.commons.utils.Pair;
import org.deegree.commons.xml.XMLAdapter;
import org.deegree.commons.xml.XPath;
import org.deegree.cs.coordinatesystems.ICRS;
import org.deegree.cs.persistence.CRSManager;
import org.deegree.geometry.Envelope;
import org.deegree.geometry.standard.DefaultEnvelope;
import org.deegree.geometry.standard.primitive.DefaultPoint;
import org.deegree.process.jaxb.java.ComplexFormatType;
import org.deegree.process.jaxb.java.ComplexInputDefinition;
import org.deegree.process.jaxb.java.ProcessletInputDefinition;
import org.deegree.rendering.r2d.RenderHelper;
import org.deegree.services.wps.ProcessletException;
import org.deegree.services.wps.ProcessletInputs;
import org.deegree.services.wps.input.ComplexInput;
import org.deegree.services.wps.input.ProcessletInput;
import org.deegree.services.wps.provider.jrxml.JrxmlUtils;
import org.deegree.services.wps.provider.jrxml.ParameterDescription;
import org.deegree.services.wps.provider.jrxml.contentprovider.AbstractJrxmlContentProvider;
import org.deegree.services.wps.provider.jrxml.jaxb.map.AbstractDatasourceType;
import org.deegree.services.wps.provider.jrxml.jaxb.map.Center;
import org.deegree.services.wps.provider.jrxml.jaxb.map.Detail;
import org.deegree.services.wps.provider.jrxml.jaxb.map.Layer;
import org.deegree.services.wps.provider.jrxml.jaxb.map.WFSDatasource;
import org.deegree.services.wps.provider.jrxml.jaxb.map.WMSDatasource;
import org.deegree.workspace.Workspace;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.sun.media.jai.codec.ImageCodec;
import com.sun.media.jai.codec.PNGEncodeParam;

/**
 * Provides a map in the jrxml
 * 
 * @author <a href="mailto:goltz@lat-lon.de">Lyn Goltz</a>
 * @author last edited by: $Author: lyn $
 * 
 * @version $Revision: $, $Date: $
 */
public class MapContentProvider extends AbstractJrxmlContentProvider {

    public MapContentProvider(Workspace workspace) {
        super(workspace);
    }

    private static final Logger LOG = LoggerFactory.getLogger(MapContentProvider.class);

    final static String SCHEMA = "http://www.deegree.org/processprovider/map";

    final static String MIME_TYPE = "text/xml";

    private static final String PARAM_PREFIX = "map";

    public static final double INCH2M = 0.0254;

    private enum SUFFIXES {

        LEGEND_SUFFIX("_legend"), MAP_SUFFIX("_img"), SCALE_SUFFIX("_scale"), LAYERLIST_SUFFIX(
                "_layerlist"), SCALEBAR_SUFFIX("_scalebar");

        private final String text;

        private SUFFIXES(String text) {
            this.text = text;
        }

    }

    @Override
    public void inspectInputParametersFromJrxml(Map<String, ParameterDescription> parameterDescriptions,
            List<JAXBElement<? extends ProcessletInputDefinition>> inputs, XMLAdapter jrxmlAdapter,
            Map<String, String> parameters, List<String> handledParameters) {
        // for a wms, parameters starting with 'map' are important. three different types are supported:
        // * wmsXYZ_map -> as image parameter
        // * wmsXYZ_legend -> as imgage parameter
        // * wmsXYZ_layerList -> as frame key
        // where XYZ is a string which is the identifier of the process parameter.
        Map<String, Pair<Integer, Integer>> mapIds = new HashMap<String, Pair<Integer, Integer>>();
        for (String parameterName : parameters.keySet()) {
            if (!handledParameters.contains(parameterName)) {
                if (isMapParameter(parameterName)) {
                    String mapId = getIdentifierFromParameter(parameterName);
                    OMElement imgElement = jrxmlAdapter.getElement(jrxmlAdapter.getRootElement(),
                            new XPath(".//jasper:image[jasper:imageExpression/text()='$P{" + parameterName
                                    + "}']/jasper:reportElement", JrxmlUtils.nsContext));
                    if (imgElement != null) {
                        if (parameterName.endsWith(SUFFIXES.MAP_SUFFIX.text)) {

                            if (!mapIds.containsKey(mapId)) {
                                int width = jrxmlAdapter.getRequiredNodeAsInteger(imgElement,
                                        new XPath("@width", nsContext));
                                int height = jrxmlAdapter.getRequiredNodeAsInteger(imgElement,
                                        new XPath("@height", nsContext));
                                mapIds.put(mapId, new Pair<Integer, Integer>(width, height));
                            }
                        }
                    }
                    if (!mapIds.containsKey(mapId)) {
                        mapIds.put(mapId, null);
                    }
                    // TODO: maybe a status information would be the better way?
                    // remove used parameter
                    handledParameters.add(parameterName);
                }
            }
        }

        for (String mapId : mapIds.keySet()) {
            LOG.debug("Found map component with id " + mapId);
            ComplexInputDefinition comp = new ComplexInputDefinition();
            addInput(comp, parameterDescriptions, mapId, 1, 0);
            ComplexFormatType format = new ComplexFormatType();
            format.setEncoding("UTF-8");
            format.setMimeType(MIME_TYPE);
            format.setSchema(SCHEMA);
            comp.setDefaultFormat(format);
            // TODO: Metadata (width/height)
            // Pair<Integer, Integer> mapDim = mapIds.get( mapId );
            // if ( mapDim != null ) {
            // System.out.println( mapDim.getFirst() );
            // System.out.println( mapDim.getSecond() );
            // try {
            // Metadata meta = new Metadata();
            // MapMetadata mm = new MapMetadata();
            // mm.setWidth( BigInteger.valueOf( mapDim.getFirst().longValue() ) );
            // mm.setHeight( BigInteger.valueOf( mapDim.getSecond().longValue() ) );
            // JAXBContext jc = JAXBContext.newInstance( MapMetadata.class );
            // Marshaller marshaller = jc.createMarshaller();
            // ByteArrayOutputStream os = new ByteArrayOutputStream();
            // XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter( os );
            // marshaller.marshal( mm, writer );
            // os.close();
            // writer.close();
            // InputStream is = new ByteArrayInputStream( os.toByteArray() );
            // XMLStreamReader reader = XMLInputFactory.newInstance().createXMLStreamReader( is );
            // StAXOMBuilder b = new StAXOMBuilder( reader );
            // OMElement elem = b.getDocumentElement();
            // System.out.println( elem );
            // meta.setAny( elem );
            // comp.getMetadata().add( meta );
            // is.close();
            // reader.close();
            // } catch ( Exception e ) {
            // LOG.error( "Could not create map metadata", e );
            // }
            // }
            inputs.add(new JAXBElement<ComplexInputDefinition>(new QName("ProcessInput"),
                    ComplexInputDefinition.class, comp));
        }

    }

    private String getIdentifierFromParameter(String parameter) {
        if (isMapParameter(parameter)) {
            for (SUFFIXES suf : SUFFIXES.values()) {
                if (parameter.endsWith(suf.text)) {
                    parameter = parameter.substring(PARAM_PREFIX.length(), parameter.length() - suf.text.length());
                }
            }
        }
        return parameter;
    }

    private String getParameterFromIdentifier(String mapId, SUFFIXES suffix) {
        return PARAM_PREFIX + mapId + suffix.text;
    }

    private boolean isMapParameter(String imgParameter) {
        boolean hasSuffix = false;
        for (SUFFIXES suf : SUFFIXES.values()) {
            if (imgParameter.endsWith(suf.text)) {
                hasSuffix = true;
            }
        }
        return hasSuffix && imgParameter.startsWith(PARAM_PREFIX);
    }

    @Override
    public Pair<InputStream, Boolean> prepareJrxmlAndReadInputParameters(InputStream jrxml,
            Map<String, Object> params, ProcessletInputs in, List<CodeType> processedIds,
            Map<String, String> parameters) throws ProcessletException {
        for (ProcessletInput input : in.getParameters()) {
            if (!processedIds.contains(input.getIdentifier()) && input instanceof ComplexInput) {
                ComplexInput complexIn = (ComplexInput) input;
                if (SCHEMA.equals(complexIn.getSchema()) && MIME_TYPE.equals(complexIn.getMimeType())) {

                    String mapId = complexIn.getIdentifier().getCode();
                    LOG.debug("Found input parameter " + mapId + " representing a map!");

                    try {
                        // JAXBContext jc = JAXBContext.newInstance( "org.deegree.services.wps.provider.jrxml.jaxb.map"
                        // );
                        JAXBContext jc = JAXBContext
                                .newInstance(org.deegree.services.wps.provider.jrxml.jaxb.map.Map.class);
                        Unmarshaller unmarshaller = jc.createUnmarshaller();
                        org.deegree.services.wps.provider.jrxml.jaxb.map.Map map = (org.deegree.services.wps.provider.jrxml.jaxb.map.Map) unmarshaller
                                .unmarshal(complexIn.getValueAsXMLStream());

                        XMLAdapter jrxmlAdapter = new XMLAdapter(jrxml);
                        OMElement root = jrxmlAdapter.getRootElement();

                        List<OrderedDatasource<?>> datasources = anaylizeRequestOrder(
                                map.getDatasources().getWMSDatasourceOrWFSDatasource());

                        // MAP
                        int resolution = map.getResolution().intValue();
                        String mapKey = getParameterFromIdentifier(mapId, SUFFIXES.MAP_SUFFIX);
                        if (parameters.containsKey(mapKey)) {
                            OMElement mapImgRep = jrxmlAdapter.getElement(root,
                                    new XPath(".//jasper:image[jasper:imageExpression/text()='$P{" + mapKey
                                            + "}']/jasper:reportElement", nsContext));
                            if (mapImgRep == null) {
                                LOG.info(
                                        "Could not find image tag in the jrxml for map parameter '" + mapKey + "'");
                                break;
                            }
                            int originalWidth = jrxmlAdapter.getRequiredNodeAsInteger(mapImgRep,
                                    new XPath("@width", nsContext));
                            int originalHeight = jrxmlAdapter.getRequiredNodeAsInteger(mapImgRep,
                                    new XPath("@height", nsContext));

                            int width = adjustSpan(originalWidth, resolution);
                            int height = adjustSpan(originalHeight, resolution);

                            Envelope bbox = calculateBBox(map.getDetail(), mapId, width, height, resolution);

                            params.put(mapKey, prepareMap(datasources, parameters.get(mapKey), originalWidth,
                                    originalHeight, width, height, bbox, resolution));
                            // SCALE
                            double scale = RenderHelper.calcScaleWMS130(width, height, bbox,
                                    bbox.getCoordinateSystem(), MapUtils.DEFAULT_PIXEL_SIZE);
                            String scaleKey = getParameterFromIdentifier(mapId, SUFFIXES.SCALE_SUFFIX);
                            if (parameters.containsKey(scaleKey)) {
                                params.put(scaleKey, convert(scale, parameters.get(scaleKey)));
                            }

                            // SCALEBAR
                            String scalebarKey = getParameterFromIdentifier(mapId, SUFFIXES.SCALEBAR_SUFFIX);
                            if (parameters.containsKey(scalebarKey)) {
                                prepareScaleBar(params, scalebarKey, jrxmlAdapter, datasources,
                                        parameters.get(scalebarKey), bbox, width);
                            }
                        }

                        // LAYERLIST
                        prepareLayerlist(getParameterFromIdentifier(mapId, SUFFIXES.LAYERLIST_SUFFIX), jrxmlAdapter,
                                map, datasources);

                        // LEGEND
                        String legendKey = getParameterFromIdentifier(mapId, SUFFIXES.LEGEND_SUFFIX);
                        if (parameters.containsKey(legendKey)) {
                            params.put(legendKey, prepareLegend(legendKey, jrxmlAdapter, datasources,
                                    parameters.get(legendKey), resolution));
                        }

                        ByteArrayOutputStream bos = new ByteArrayOutputStream();
                        try {
                            if (LOG.isTraceEnabled()) {
                                LOG.trace("Adjusted jrxml: " + jrxmlAdapter.getRootElement());
                            }
                            jrxmlAdapter.getRootElement().serialize(bos);
                            jrxml = new ByteArrayInputStream(bos.toByteArray());
                        } catch (XMLStreamException e) {
                            throw new RuntimeException(e);
                        } finally {
                            IOUtils.closeQuietly(bos);
                        }

                    } catch (JAXBException e) {
                        String msg = "Could not parse map configuration from parameter '"
                                + complexIn.getIdentifier() + "': " + e.getMessage();
                        LOG.debug(msg, e);
                        throw new ProcessletException(msg);
                    } catch (IOException e) {
                        String msg = "Could not read map configuration from parameter '" + complexIn.getIdentifier()
                                + "': " + e.getMessage();
                        LOG.debug(msg, e);
                        throw new ProcessletException(msg);
                    } catch (XMLStreamException e) {
                        String msg = "Could not read map configuration as xml from parameter '"
                                + complexIn.getIdentifier() + "': " + e.getMessage();
                        LOG.debug(msg, e);
                        throw new ProcessletException(msg);
                    }
                    processedIds.add(input.getIdentifier());
                }
            }
        }
        return new Pair<InputStream, Boolean>(jrxml, false);
    }

    private Envelope calculateBBox(Detail detail, String id, int mapWidth, int mapHeight, int dpi)
            throws ProcessletException {
        ICRS crs = CRSManager.getCRSRef(detail.getCrs());
        Center center = detail.getCenter();
        Envelope bbox = null;
        if (center != null) {
            int scaleDenominator = center.getScaleDenominator().intValue();
            String[] coords = center.getValue().split(",");
            double pixelSize = INCH2M / dpi;
            double w2 = (scaleDenominator * pixelSize * mapWidth) / 2d;
            double x1 = Double.parseDouble(coords[0]) - w2;
            double x2 = Double.parseDouble(coords[0]) + w2;
            w2 = (scaleDenominator * pixelSize * mapHeight) / 2d;
            double y1 = Double.parseDouble(coords[1]) - w2;
            double y2 = Double.parseDouble(coords[1]) + w2;
            bbox = new DefaultEnvelope(null, crs, null, new DefaultPoint(null, crs, null, new double[] { x1, y1 }),
                    new DefaultPoint(null, crs, null, new double[] { x2, y2 }));
        } else if (detail.getBbox() != null) {
            String[] coords = detail.getBbox().split(",");
            double bboxXmin = Double.parseDouble(coords[0]);
            double bboxYmin = Double.parseDouble(coords[1]);
            double bboxXmax = Double.parseDouble(coords[2]);
            double bboxYmax = Double.parseDouble(coords[3]);
            double bboxWidth = bboxXmax - bboxXmin;
            double bboxHeight = bboxYmax - bboxYmin;
            double bboxAspectRatio = bboxWidth / bboxHeight;
            double printAspectRatio = ((double) mapWidth) / mapHeight;
            if (bboxAspectRatio > printAspectRatio) {
                double centerY = bboxYmin + ((bboxYmax - bboxYmin) / 2d);
                double height = bboxWidth * (1.0 / printAspectRatio);
                double minY = centerY - (height / 2d);
                double maxY = centerY + (height / 2d);
                bbox = new DefaultEnvelope(null, crs, null,
                        new DefaultPoint(null, crs, null, new double[] { bboxXmin, minY }),
                        new DefaultPoint(null, crs, null, new double[] { bboxXmax, maxY }));
            } else {
                double centerX = bboxXmin + ((bboxXmax - bboxXmin) / 2d);
                double width = bboxHeight * printAspectRatio;
                double minX = centerX - (width / 2d);
                double maxX = centerX + (width / 2d);
                bbox = new DefaultEnvelope(null, crs, null,
                        new DefaultPoint(null, crs, null, new double[] { minX, bboxYmin }),
                        new DefaultPoint(null, crs, null, new double[] { maxX, bboxYmax }));
            }
        } else {
            throw new ProcessletException(
                    "Could not parse required parameter bbox or center for map component with id  '" + id + "'.");
        }
        LOG.debug("requested and adjusted bbox: {}", bbox);
        return bbox;
    }

    private void prepareScaleBar(Map<String, Object> params, String scalebarKey, XMLAdapter jrxmlAdapter,
            List<OrderedDatasource<?>> datasources, String type, Envelope bbox, double mapWidth)
            throws ProcessletException {

        OMElement sbRep = jrxmlAdapter.getElement(jrxmlAdapter.getRootElement(), new XPath(
                ".//jasper:image[jasper:imageExpression/text()='$P{" + scalebarKey + "}']/jasper:reportElement",
                nsContext));

        if (sbRep != null) {
            // TODO: rework this!
            LOG.debug("Found scalebar with key '" + scalebarKey + "'.");
            int w = jrxmlAdapter.getRequiredNodeAsInteger(sbRep, new XPath("@width", nsContext));
            int h = jrxmlAdapter.getRequiredNodeAsInteger(sbRep, new XPath("@height", nsContext));
            BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
            Graphics2D g = img.createGraphics();

            String fontName = null;
            int fontSize = 8;
            int desiredWidth = w - 30;
            // calculate scale bar max scale and size
            int length = 0;
            double lx = 0;
            double scale = 0;
            for (int i = 0; i < 100; i++) {
                double k = 0;
                double dec = 30 * Math.pow(10, i);
                for (int j = 0; j < 9; j++) {
                    k += dec;
                    double tx = -k * (mapWidth / bbox.getSpan0());
                    if (Math.abs(tx - lx) < desiredWidth) {
                        length = (int) Math.round(Math.abs(tx - lx));
                        scale = k;
                    } else {
                        break;
                    }
                }
            }
            // draw scale bar base line
            g.setStroke(new BasicStroke((desiredWidth + 30) / 250));
            g.setColor(Color.black);
            g.drawLine(10, 30, length + 10, 30);
            double dx = length / 3d;
            double vdx = scale / 3;
            double div = 1;
            String uom = "m";
            if (scale > 1000) {
                div = 1000;
                uom = "km";
            }
            // draw scale bar scales
            if (fontName == null) {
                fontName = "SANS SERIF";
            }
            g.setFont(new Font(fontName, Font.PLAIN, fontSize));
            DecimalFormat df = new DecimalFormat("##.# ");
            DecimalFormat dfWithUom = new DecimalFormat("##.# " + uom);
            for (int i = 0; i < 4; i++) {
                String label = i < 3 ? df.format((vdx * i) / div) : dfWithUom.format((vdx * i) / div);
                g.drawString(label, (int) Math.round(10 + i * dx) - 8, 10);
                g.drawLine((int) Math.round(10 + i * dx), 30, (int) Math.round(10 + i * dx), 20);
            }
            for (int i = 0; i < 7; i++) {
                g.drawLine((int) Math.round(10 + i * dx / 2d), 30, (int) Math.round(10 + i * dx / 2d), 25);
            }
            g.dispose();
            params.put(scalebarKey, convertImageToReportFormat(type, img));
            LOG.debug("added scalebar");
        }
    }

    private Object convert(double scale, String parameterType) {
        if (parameterType == null || "java.lang.String".equals(parameterType)) {
            return Double.toString(scale);
        } else if ("java.lang.Integer".equals(parameterType)) {
            return new Double(scale).intValue();
        } else if ("java.lang.Float".equals(parameterType)) {
            return new Double(scale).floatValue();
        } else if ("java.lang.Long".equals(parameterType)) {
            return new Double(scale).longValue();
        } else if ("java.math.BigDecimal".equals(parameterType)) {
            return new BigDecimal(scale);
        } else if ("java.lang.Short".equals(parameterType)) {
            return new Double(scale).shortValue();
        } else if ("java.lang.Byte".equals(parameterType)) {
            return new Double(scale).byteValue();
        }
        return scale;
    }

    private Object prepareLegend(String legendKey, XMLAdapter jrxmlAdapter, List<OrderedDatasource<?>> datasources,
            String type, int resolution) throws ProcessletException {

        if ("net.sf.jasperreports.engine.JRRenderable".equals(type)) {
            return new LegendRenderable(datasources, resolution);
        } else {
            OMElement legendRE = jrxmlAdapter.getElement(jrxmlAdapter.getRootElement(), new XPath(
                    ".//jasper:image[jasper:imageExpression/text()='$P{" + legendKey + "}']/jasper:reportElement",
                    nsContext));

            if (legendRE != null) {
                LOG.debug("Found legend with key '" + legendKey + "'.");
                int width = jrxmlAdapter.getRequiredNodeAsInteger(legendRE, new XPath("@width", nsContext));
                int height = jrxmlAdapter.getRequiredNodeAsInteger(legendRE, new XPath("@height", nsContext));
                width = adjustSpan(width, resolution);
                height = adjustSpan(height, resolution);

                BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
                Graphics2D g = bi.createGraphics();
                // TODO: bgcolor?
                Color bg = Color.decode("0xFFFFFF");
                g.setColor(bg);
                g.fillRect(0, 0, width, height);
                g.setColor(Color.BLACK);
                int k = 0;

                for (int i = 0; i < datasources.size(); i++) {
                    if (k > height) {
                        LOG.warn("The necessary legend size is larger than the available legend space.");
                    }
                    List<Pair<String, BufferedImage>> legends = datasources.get(i).getLegends(width);
                    for (Pair<String, BufferedImage> legend : legends) {
                        BufferedImage img = legend.second;
                        if (img != null) {
                            if (img.getWidth(null) < 50) {
                                // it is assumed that no label is assigned
                                g.drawImage(img, 0, k, null);
                                g.drawString(legend.first, img.getWidth(null) + 10, k + img.getHeight(null) / 2);
                            } else {
                                g.drawImage(img, 0, k, null);
                            }
                            k = k + img.getHeight(null) + 10;
                        } else {
                            g.drawString("- " + legend.first, 0, k + 10);
                            k = k + 20;
                        }
                    }
                }
                g.dispose();
                return convertImageToReportFormat(type, bi);
            }
        }
        return null;
    }

    private void prepareLayerlist(String layerListKey, XMLAdapter jrxmlAdapter,
            org.deegree.services.wps.provider.jrxml.jaxb.map.Map map, List<OrderedDatasource<?>> datasources) {

        OMElement layerListFrame = jrxmlAdapter.getElement(jrxmlAdapter.getRootElement(), new XPath(
                ".//jasper:band/jasper:frame[jasper:reportElement/@key='" + layerListKey + "']", nsContext));
        if (layerListFrame != null) {
            LOG.debug("Found layer list with key '{}' to adjust.", layerListKey);
            List<OMElement> elements = jrxmlAdapter.getElements(layerListFrame,
                    new XPath("jasper:staticText", nsContext));
            if (elements.size() > 1) {
                OMElement grpTemplate = elements.get(0);
                OMElement fieldTemplate = elements.get(1);
                for (OMElement element : elements) {
                    element.detach();
                }
                XMLAdapter grpAdapter = new XMLAdapter(grpTemplate);
                int grpHeight = grpAdapter.getNodeAsInt(grpTemplate,
                        new XPath("jasper:reportElement/@height", nsContext), 15);
                int grpY = grpAdapter.getNodeAsInt(grpTemplate, new XPath("jasper:reportElement/@y", nsContext), 0);

                XMLAdapter fieldAdapter = new XMLAdapter(fieldTemplate);
                int fieldHeight = fieldAdapter.getNodeAsInt(fieldTemplate,
                        new XPath("jasper:reportElement/@height", nsContext), 15);
                OMFactory factory = OMAbstractFactory.getOMFactory();
                // y + height * index
                int y = grpY;
                for (OrderedDatasource<?> datasource : datasources) {
                    for (String datasourceKey : datasource.getLayerList().keySet()) {
                        OMElement newGrp = createLayerEntry(grpTemplate, y, factory, datasourceKey);
                        layerListFrame.addChild(newGrp);
                        y += grpHeight;
                        for (String layerName : datasource.getLayerList().get(datasourceKey)) {
                            OMElement newField = createLayerEntry(fieldTemplate, y, factory, layerName);
                            layerListFrame.addChild(newField);
                            y += fieldHeight;
                        }
                    }
                }
            } else {
                LOG.info("layerlist frame with key '{}' must have at least two child elements", layerListKey);
            }
        } else {
            LOG.debug("no layer list with key '{}' found.", layerListKey);
        }
    }

    private OMElement createLayerEntry(OMElement template, int y, OMFactory factory, String text) {
        OMElement newGrp = template.cloneOMElement();
        OMElement e = newGrp.getFirstChildWithName(new QName(JASPERREPORTS_NS, "reportElement"));
        e.addAttribute("y", Integer.toString(y), null);
        e = newGrp.getFirstChildWithName(new QName(JASPERREPORTS_NS, "text"));
        // this does not work:
        // e.setText( layer );
        // it attaches the text, but does not replace
        e.getFirstOMChild().detach();
        e.addChild(factory.createOMText(e, text));
        return newGrp;
    }

    private Object prepareMap(List<OrderedDatasource<?>> datasources, String type, int originalWidth,
            int originalHeight, int width, int height, Envelope bbox, int resolution) throws ProcessletException {
        if ("net.sf.jasperreports.engine.JRRenderable".equals(type)) {
            return new MapRenderable(datasources, bbox, resolution);
        } else {
            BufferedImage bi = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
            Graphics g = bi.getGraphics();
            for (OrderedDatasource<?> datasource : datasources) {
                BufferedImage image = datasource.getImage(width, height, bbox);
                if (image != null) {
                    g.drawImage(image, 0, 0, null);
                }
            }
            g.dispose();
            return convertImageToReportFormat(type, bi);
        }
    }

    private Object convertImageToReportFormat(String type, BufferedImage bi) throws ProcessletException {
        if (type != null && type.equals("java.io.File")) {
            return writeImage(bi);
        } else if (type != null && type.equals("java.net.URL")) {
            try {
                return writeImage(bi).toURI().toURL();
            } catch (MalformedURLException e) {
                LOG.error("Could not create url: ", e);
            }
        } else if (type != null && type.equals("java.io.InputStream")) {
            try {
                return new FileInputStream(writeImage(bi));
            } catch (FileNotFoundException e) {
                LOG.error("Could not open stream: ", e);
            }
        } else if (type != null && type.equals("java.awt.Image")) {
            return bi;
        }

        return writeImage(bi).toString();
    }

    List<OrderedDatasource<?>> anaylizeRequestOrder(List<AbstractDatasourceType> datasources) {

        List<OrderedDatasource<?>> orderedDatasources = new ArrayList<OrderedDatasource<?>>();
        for (AbstractDatasourceType datasource : datasources) {

            if (datasource instanceof WMSDatasource) {
                WMSDatasource wmsDatasource = (WMSDatasource) datasource;
                List<Layer> layers = wmsDatasource.getLayers().getLayer();
                int index = 0;
                for (Layer layer : layers) {
                    BigInteger position = layer.getPosition();
                    if (position != null) {
                        int intPos = position.intValue();

                        WMSOrderedDatasource contains = contains(orderedDatasources, wmsDatasource, intPos);
                        // splitten! wenn position zwischen zwei
                        if (contains != null) {
                            List<Layer> orderedLayers = contains.layers;
                            int i = 0;
                            for (Layer orderedLayer : orderedLayers) {
                                if (orderedLayer.getPosition().intValue() < intPos) {
                                    i++;
                                }
                            }
                            contains.layers.add(i, layer);
                            contains.min = contains.min < intPos ? contains.min : intPos;
                            contains.max = contains.max > intPos ? contains.max : intPos;
                        } else {
                            List<Layer> newLayerList = new ArrayList<Layer>();
                            newLayerList.add(layer);
                            int indexToAdd = getIndexToAdd(orderedDatasources, intPos);
                            orderedDatasources.add(indexToAdd,
                                    new WMSOrderedDatasource(wmsDatasource, newLayerList, intPos, intPos));
                        }
                    } else {
                        WMSOrderedDatasource contains = contains(orderedDatasources, wmsDatasource);
                        if (contains != null) {
                            contains.layers.add(layer);
                        } else {
                            List<Layer> newLayerList = new ArrayList<Layer>();
                            newLayerList.add(layer);
                            orderedDatasources.add(new WMSOrderedDatasource(wmsDatasource, newLayerList));
                        }
                    }
                    index++;
                }
            } else if (datasource instanceof WFSDatasource) {
                WFSDatasource wfsDatasource = (WFSDatasource) datasource;
                WFSOrderedDatasource newDatasource = new WFSOrderedDatasource(wfsDatasource);
                if (wfsDatasource.getPosition() != null) {
                    int indexToAdd = getIndexToAdd(orderedDatasources, wfsDatasource.getPosition().intValue());
                    orderedDatasources.add(indexToAdd, newDatasource);
                } else {
                    orderedDatasources.add(newDatasource);
                }

            }
        }
        return orderedDatasources;
    }

    private int getIndexToAdd(List<OrderedDatasource<?>> orderedDatasources, int intPos) {
        int indexToAdd = 0;
        boolean isBetween = false;
        for (OrderedDatasource<?> orderedDatasource : orderedDatasources) {
            if (orderedDatasource.min > 0 && intPos > orderedDatasource.min && intPos < orderedDatasource.max) {
                indexToAdd++;
                isBetween = true;
                break;
            } else if (orderedDatasource.min > 0 && intPos > orderedDatasource.max) {
                indexToAdd++;
            } else {
                break;
            }
        }
        if (isBetween) {
            OrderedDatasource<?> dsToSplit = orderedDatasources.get(indexToAdd - 1);
            if (dsToSplit instanceof WMSOrderedDatasource) {
                List<Layer> newLayerListFromSplit = new ArrayList<Layer>();
                int newMinPos = 0;
                int newMaxPos = 0;
                for (Layer l : ((WMSOrderedDatasource) dsToSplit).layers) {
                    if (l.getPosition() != null && l.getPosition().intValue() > intPos) {
                        newLayerListFromSplit.add(l);
                        newMinPos = Math.min(l.getPosition().intValue(), newMinPos);
                        newMaxPos = Math.max(l.getPosition().intValue(), newMaxPos);
                    }
                }
                for (Layer lToRemove : newLayerListFromSplit) {
                    ((WMSOrderedDatasource) dsToSplit).layers.remove(lToRemove);
                }
                orderedDatasources.add(indexToAdd, new WMSOrderedDatasource((WMSDatasource) dsToSplit.datasource,
                        newLayerListFromSplit, intPos, intPos));
            }
        }
        return indexToAdd;
    }

    private WMSOrderedDatasource contains(List<OrderedDatasource<?>> orderedDatasources, WMSDatasource datasource,
            int index) {
        int i = 0;
        for (OrderedDatasource<?> orderedDatasource : orderedDatasources) {
            if (orderedDatasource.datasource.equals(datasource)) {
                WMSOrderedDatasource wmsOrderedDatasource = (WMSOrderedDatasource) orderedDatasource;
                int maxBefore = Integer.MIN_VALUE;
                int minNext = Integer.MIN_VALUE;
                if (i - 1 > 0 && i - 1 < orderedDatasources.size()) {
                    maxBefore = orderedDatasources.get(i - 1).max;
                }
                if (i + 1 < orderedDatasources.size()) {
                    minNext = orderedDatasources.get(i + 1).min;
                }
                if (index > maxBefore && (minNext < 0 || index < minNext)) {
                    return wmsOrderedDatasource;
                }
            }
            i++;
        }
        return null;
    }

    private WMSOrderedDatasource contains(List<OrderedDatasource<?>> orderedDatasources, WMSDatasource datasource) {
        for (OrderedDatasource<?> orderedDatasource : orderedDatasources) {
            if (orderedDatasource.datasource.equals(datasource)) {
                return (WMSOrderedDatasource) orderedDatasource;
            }
        }
        return null;
    }

    private File writeImage(BufferedImage bi) throws ProcessletException {
        FileOutputStream fos = null;
        try {
            File f = File.createTempFile("image", ".png");
            fos = new FileOutputStream(f);

            PNGEncodeParam encodeParam = PNGEncodeParam.getDefaultEncodeParam(bi);

            if (encodeParam instanceof PNGEncodeParam.Palette) {
                PNGEncodeParam.Palette p = (PNGEncodeParam.Palette) encodeParam;
                byte[] b = new byte[] { -127 };
                p.setPaletteTransparency(b);
            }
            com.sun.media.jai.codec.ImageEncoder encoder = ImageCodec.createImageEncoder("PNG", fos, encodeParam);
            encoder.encode(bi.getData(), bi.getColorModel());
            LOG.debug("Wrote image to file: {}", f.toString());
            return f;
        } catch (IOException e) {
            String msg = "Could not write image to file: " + e.getMessage();
            LOG.debug(msg, e);
            throw new ProcessletException(msg);
        } finally {
            IOUtils.closeQuietly(fos);
        }
    }

}