    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wmps/DefaultGetMapHandler.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005     Department of Geography, University of Bonn
    006     and
    007     lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035     ----------------------------------------------------------------------------*/
    036    package org.deegree.ogcwebservices.wmps;
    037
    038    import java.awt.Color;
    039    import java.awt.Font;
    040    import java.awt.Graphics;
    041    import java.awt.Graphics2D;
    042    import java.awt.RenderingHints;
    043    import java.awt.image.BufferedImage;
    044    import java.util.ArrayList;
    045    import java.util.List;
    046
    047    import org.deegree.framework.log.ILogger;
    048    import org.deegree.framework.log.LoggerFactory;
    049    import org.deegree.framework.util.CharsetUtils;
    050    import org.deegree.framework.util.ImageUtils;
    051    import org.deegree.framework.util.MapUtils;
    052    import org.deegree.framework.util.StringTools;
    053    import org.deegree.framework.xml.XMLParsingException;
    054    import org.deegree.graphics.MapFactory;
    055    import org.deegree.graphics.MapView;
    056    import org.deegree.graphics.Theme;
    057    import org.deegree.graphics.optimizers.LabelOptimizer;
    058    import org.deegree.graphics.sld.AbstractLayer;
    059    import org.deegree.graphics.sld.AbstractStyle;
    060    import org.deegree.graphics.sld.NamedLayer;
    061    import org.deegree.graphics.sld.NamedStyle;
    062    import org.deegree.graphics.sld.SLDFactory;
    063    import org.deegree.graphics.sld.StyledLayerDescriptor;
    064    import org.deegree.graphics.sld.UserLayer;
    065    import org.deegree.graphics.sld.UserStyle;
    066    import org.deegree.i18n.Messages;
    067    import org.deegree.model.crs.CRSFactory;
    068    import org.deegree.model.crs.CoordinateSystem;
    069    import org.deegree.model.crs.GeoTransformer;
    070    import org.deegree.model.spatialschema.Envelope;
    071    import org.deegree.model.spatialschema.Geometry;
    072    import org.deegree.model.spatialschema.GeometryFactory;
    073    import org.deegree.ogcbase.InvalidSRSException;
    074    import org.deegree.ogcwebservices.OGCWebServiceException;
    075    import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration;
    076    import org.deegree.ogcwebservices.wmps.configuration.WMPSDeegreeParams;
    077    import org.deegree.ogcwebservices.wmps.operation.PrintMap;
    078    import org.deegree.ogcwebservices.wms.LayerNotDefinedException;
    079    import org.deegree.ogcwebservices.wms.StyleNotDefinedException;
    080    import org.deegree.ogcwebservices.wms.capabilities.Layer;
    081    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
    082    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
    083    import org.deegree.ogcwebservices.wms.operation.GetMap;
    084
    085    /**
    086     * This is a copy of the WMS package.
    087     *
    088     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    089     * @author last edited by: $Author: apoth $
    090     *
    091     * @version $Revision: 21104 $, $Date: 2009-11-27 14:33:04 +0100 (Fr, 27. Nov 2009) $
    092     */
    093    public class DefaultGetMapHandler implements GetMapHandler {
    094
    095        private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
    096
    097        protected GetMap request;
    098
    099        private Object[] themes;
    100
    101        protected double scale = 0;
    102
    103        private int count = 0;
    104
    105        protected CoordinateSystem reqCRS;
    106
    107        private WMPSConfiguration configuration;
    108
    109        private BufferedImage copyrightImg;
    110
    111        private Graphics graph;
    112
    113        /**
    114         * Creates a new GetMapHandler object.
    115         *
    116         * @param configuration
    117         * @param request
    118         *            request to perform
    119         * @throws OGCWebServiceException
    120         */
    121        public DefaultGetMapHandler( WMPSConfiguration configuration, GetMap request ) throws OGCWebServiceException {
    122            this.request = request;
    123            this.configuration = configuration;
    124
    125            try {
    126                // get copyright image if possible
    127                this.copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyright() );
    128            } catch ( Exception e ) {
    129                // eat it
    130            }
    131
    132            try {
    133                this.reqCRS = CRSFactory.create( this.request.getSrs() );
    134            } catch ( Exception e ) {
    135                throw new InvalidSRSException( "SRS: " + request.getSrs() + "is nor known by the deegree WMS" );
    136            }
    137
    138        }
    139
    140        /**
    141         * returns the configuration used by the handler
    142         *
    143         * @return WMPSConfiguration
    144         */
    145        public WMPSConfiguration getConfiguration() {
    146            return this.configuration;
    147        }
    148
    149        /**
    150         * increases the counter variable that holds the number of services that has sent a response. All data are available
    151         * if the counter value equals the number of requested layers.
    152         */
    153        protected synchronized void increaseCounter() {
    154            this.count++;
    155        }
    156
    157        /**
    158         * performs a GetMap request and returns the result encapsulated within a <tt>GetMapResult</tt> object.
    159         * <p>
    160         * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
    161         * impossible to return a result. If something went wrong performing the request (none fatal error) The exception
    162         * shall be encapsulated within the response object to be returned to the client as requested (GetMap-Request
    163         * EXCEPTION-Parameter).
    164         *
    165         * @param printMap
    166         * @param g
    167         * @throws OGCWebServiceException
    168         */
    169        public void performGetMap( PrintMap printMap, Graphics g )
    170                                throws OGCWebServiceException {
    171
    172            this.graph = g;
    173
    174            try {
    175                CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
    176                double dpi = printMap.getDpi();
    177                if ( dpi < 0 ) {
    178                    dpi = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
    179                }
    180                double pixelSize = WMPSConfiguration.INCH2M / dpi;
    181                this.scale = MapUtils.calcScale( this.request.getWidth(), this.request.getHeight(),
    182                                                 this.request.getBoundingBox(), crs, pixelSize );
    183                LOG.logInfo( "OGC WMPS scale denominator: " + this.scale );
    184            } catch ( Exception e ) {
    185                LOG.logDebug( "-", e );
    186                throw new OGCWebServiceException( "Couldn't calculate scale! " + e );
    187            }
    188
    189            StyledLayerDescriptor sld = null;
    190            try {
    191                sld = toSLD( this.request.getLayers(), this.request.getStyledLayerDescriptor() );
    192            } catch ( XMLParsingException e1 ) {
    193                // should never happen
    194                e1.printStackTrace();
    195            }
    196
    197            AbstractLayer[] layers = sld.getLayers();
    198            // get the number of themes assigned to the selected layers
    199            // notice that there maybe more themes as there are layers because
    200            // 1 .. n datasources can be assigned to one layer.
    201            int cntTh = countNumberOfThemes( layers, this.scale );
    202            this.themes = new Object[cntTh];
    203            // invokes the data supplyer for each layer in an independent thread
    204            int kk = 0;
    205            for ( int i = 0; i < layers.length; i++ ) {
    206                if ( layers[i] instanceof NamedLayer ) {
    207                    String styleName = null;
    208                    if ( i < this.request.getLayers().length ) {
    209                        styleName = this.request.getLayers()[i].getStyleName();
    210                    }
    211                    kk = invokeNamedLayer( layers[i], kk, styleName );
    212                } else {
    213                    GetMapServiceInvokerForUL si = new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], kk++ );
    214
    215                    si.start();
    216                }
    217            }
    218            waitForFinished();
    219            renderMap();
    220
    221        }
    222
    223        /**
    224         * Invoke the named layer
    225         *
    226         * @param layer
    227         * @param kk
    228         * @param styleName
    229         * @return int
    230         * @throws OGCWebServiceException
    231         */
    232        private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName )
    233                                throws OGCWebServiceException {
    234
    235            Layer lay = this.configuration.getLayer( layer.getName() );
    236
    237            if ( validate( lay, layer.getName() ) ) {
    238                UserStyle us = getStyles( (NamedLayer) layer, styleName );
    239                AbstractDataSource[] ds = lay.getDataSource();
    240
    241                for ( int j = 0; j < ds.length; j++ ) {
    242
    243                    ScaleHint scaleHint = ds[j].getScaleHint();
    244                    if ( this.scale >= scaleHint.getMin() && this.scale < scaleHint.getMax()
    245                         && isValidArea( ds[j].getValidArea() ) ) {
    246                        GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, lay, ds[j], us, kk++ );
    247                        si.start();
    248                    }
    249                }
    250            } else {
    251                // set theme to null if no data are available for the requested
    252                // area and/or scale
    253                this.themes[kk++] = null;
    254                increaseCounter();
    255            }
    256            return kk;
    257        }
    258
    259        /**
    260         * returns the number of <code>DataSource</code>s involved in a GetMap request
    261         *
    262         * @param layers
    263         * @param currentscale
    264         * @return int
    265         */
    266        private int countNumberOfThemes( AbstractLayer[] layers, double currentscale ) {
    267            int cnt = 0;
    268            for ( int i = 0; i < layers.length; i++ ) {
    269                if ( layers[i] instanceof NamedLayer ) {
    270                    Layer lay = this.configuration.getLayer( layers[i].getName() );
    271                    AbstractDataSource[] ds = lay.getDataSource();
    272                    for ( int j = 0; j < ds.length; j++ ) {
    273
    274                        ScaleHint scaleHint = ds[j].getScaleHint();
    275                        if ( currentscale >= scaleHint.getMin() && currentscale < scaleHint.getMax()
    276                             && isValidArea( ds[j].getValidArea() ) ) {
    277
    278                            cnt++;
    279                        }
    280                    }
    281                } else {
    282                    cnt++;
    283                }
    284            }
    285            return cnt;
    286        }
    287
    288        /**
    289         * returns true if the requested boundingbox intersects with the valid area of a datasource
    290         *
    291         * @param validArea
    292         * @return boolean
    293         */
    294        private boolean isValidArea( Geometry validArea ) {
    295
    296            if ( validArea != null ) {
    297                try {
    298                    Envelope env = this.request.getBoundingBox();
    299                    Geometry geom = GeometryFactory.createSurface( env, this.reqCRS );
    300                    if ( !this.reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
    301                        // if requested CRS is not identical to the CRS of the valid area
    302                        // a transformation must be performed before intersection can
    303                        // be checked
    304                        GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
    305                        geom = gt.transform( geom );
    306                    }
    307                    return geom.intersects( validArea );
    308                } catch ( Exception e ) {
    309                    // should never happen
    310                    LOG.logError( "could not validate WMS datasource area", e );
    311                }
    312            }
    313            return true;
    314        }
    315
    316        /**
    317         * runs a loop until all sub requestes (one for each layer) has been finished or the maximum time limit has been
    318         * exceeded.
    319         *
    320         * @throws OGCWebServiceException
    321         */
    322        private void waitForFinished()
    323                                throws OGCWebServiceException {
    324            if ( this.count < this.themes.length ) {
    325                // waits until the requested layers are available as <tt>DisplayElements</tt>
    326                // or the time limit has been reached.
    327                // if count == themes.length then no request must be performed
    328                long timeStamp = System.currentTimeMillis();
    329                long lapse = 0;
    330                long timeout = 1000 * ( this.configuration.getDeegreeParams().getRequestTimeLimit() - 1 );
    331                do {
    332                    try {
    333                        Thread.sleep( 50 );
    334                        lapse += 50;
    335                    } catch ( InterruptedException e ) {
    336                        throw new OGCWebServiceException( "GetMapHandler", "fatal exception waiting for "
    337                                                                           + "GetMapHandler results" );
    338                    }
    339                } while ( this.count < this.themes.length && lapse < timeout );
    340                if ( System.currentTimeMillis() - timeStamp >= timeout ) {
    341                    throw new OGCWebServiceException( "Processing of the GetMap request " + "exceeds timelimit" );
    342                }
    343            }
    344        }
    345
    346        /**
    347         *
    348         * @param layers
    349         * @param inSLD
    350         * @return StyledLayerDescriptor
    351         * @throws XMLParsingException
    352         */
    353        private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
    354                                throws XMLParsingException {
    355            StyledLayerDescriptor sld = null;
    356
    357            if ( layers != null && layers.length > 0 && inSLD == null ) {
    358                // Adds the content from the LAYERS and STYLES attribute to the SLD
    359                StringBuffer sb = new StringBuffer( 5000 );
    360                sb.append( "<?xml version=\"1.0\" encoding=\"" + CharsetUtils.getSystemCharset() + "\"?>" );
    361                sb.append( "<StyledLayerDescriptor version=\"1.0.0\" " );
    362                sb.append( "xmlns='http://www.opengis.net/sld'>" );
    363
    364                for ( int i = 0; i < layers.length; i++ ) {
    365                    sb.append( "<NamedLayer>" );
    366                    sb.append( "<Name>" + layers[i].getName() + "</Name>" );
    367                    sb.append( "<NamedStyle><Name>" + layers[i].getStyleName() + "</Name></NamedStyle></NamedLayer>" );
    368                }
    369                sb.append( "</StyledLayerDescriptor>" );
    370
    371                try {
    372                    sld = SLDFactory.createSLD( sb.toString() );
    373                } catch ( XMLParsingException e ) {
    374                    throw new XMLParsingException( StringTools.stackTraceToString( e ) );
    375                }
    376            } else if ( layers != null && layers.length > 0 && inSLD != null ) {
    377                // if layers not null and sld is not null then SLD layers just be
    378                // considered if present in the layers list
    379                List<String> list = new ArrayList<String>();
    380                for ( int i = 0; i < layers.length; i++ ) {
    381                    list.add( layers[i].getName() );
    382                }
    383
    384                List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
    385                AbstractLayer[] al = inSLD.getLayers();
    386                for ( int i = 0; i < al.length; i++ ) {
    387                    if ( list.contains( al[i].getName() ) ) {
    388                        newList.add( al[i] );
    389                    }
    390                }
    391                al = new AbstractLayer[newList.size()];
    392                sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
    393            } else {
    394                // if no layers are defined ...
    395                sld = inSLD;
    396            }
    397
    398            return sld;
    399        }
    400
    401        /**
    402         * returns the <tt>UserStyle</tt>s assigned to a named layer
    403         *
    404         * @param sldLayer
    405         *            layer to get the styles for
    406         * @param styleName
    407         *            requested stylename (from the KVP encoding)
    408         * @return UserStyle
    409         * @throws OGCWebServiceException
    410         */
    411        private UserStyle getStyles( NamedLayer sldLayer, String styleName )
    412                                throws OGCWebServiceException {
    413
    414            AbstractStyle[] styles = sldLayer.getStyles();
    415            UserStyle us = null;
    416
    417            // to avoid retrieving the layer again for each style
    418            Layer layer = null;
    419            layer = this.configuration.getLayer( sldLayer.getName() );
    420            int i = 0;
    421            while ( us == null && i < styles.length ) {
    422                if ( styles[i] instanceof NamedStyle ) {
    423                    // styles will be taken from the WMS's style repository
    424                    us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
    425                } else {
    426                    // if the requested style fits the name of the defined style or
    427                    // if the defined style is marked as default and the requested
    428                    // style if 'default' the condition is true. This includes that
    429                    // if more than one style with the same name or more than one
    430                    // style is marked as default always the first will be choosen
    431                    if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
    432                         || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
    433                        us = (UserStyle) styles[i];
    434                    }
    435                }
    436                i++;
    437            }
    438            if ( us == null ) {
    439                // this may happens if the SLD contains a named layer but not
    440                // a style! yes this is valid according to SLD spec 1.0.0
    441                us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
    442            }
    443            return us;
    444        }
    445
    446        /**
    447         * Returns a Predifined UserStyle
    448         *
    449         * @param styleName
    450         * @param layerName
    451         * @param layer
    452         * @return UserStyle
    453         * @throws StyleNotDefinedException
    454         */
    455        private UserStyle getPredefinedStyle( String styleName, String layerName, Layer layer )
    456                                throws StyleNotDefinedException {
    457            UserStyle us = null;
    458
    459            if ( "default".equals( styleName ) ) {
    460                us = layer.getStyle( styleName );
    461            }
    462
    463            if ( us == null ) {
    464                if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
    465                     || styleName.equals( "default" ) ) {
    466                    styleName = "default:" + layerName;
    467                }
    468            }
    469
    470            us = layer.getStyle( styleName );
    471            if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
    472                String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
    473                throw new StyleNotDefinedException( s );
    474            }
    475            return us;
    476        }
    477
    478        /**
    479         * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
    480         * be thrown. If the layer matches the request, but isn't able to deviever data for the requested area and/or scale
    481         * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
    482         * true will be returned.
    483         *
    484         * @param layer
    485         *            layer as defined at the capabilities/configuration
    486         * @param name
    487         *            name of the layer (must be submitted seperatly because the layer parameter can be <tt>null</tt>
    488         * @return boolean
    489         * @throws OGCWebServiceException
    490         */
    491        private boolean validate( Layer layer, String name )
    492                                throws OGCWebServiceException {
    493
    494            // check if layer is available
    495            if ( layer == null ) {
    496                throw new LayerNotDefinedException( "Layer: " + name + " is not known by the WMS" );
    497            }
    498
    499            if ( !layer.isSrsSupported( this.request.getSrs() ) ) {
    500                throw new InvalidSRSException( "SRS: " + this.request.getSrs() + "is not known by layer: " + name );
    501            }
    502
    503            // check for valid coordinated reference system
    504            String[] srs = layer.getSrs();
    505            boolean tmp = false;
    506            for ( int i = 0; i < srs.length; i++ ) {
    507                if ( srs[i].equalsIgnoreCase( this.request.getSrs() ) ) {
    508                    tmp = true;
    509                    break;
    510                }
    511            }
    512
    513            if ( !tmp ) {
    514                throw new InvalidSRSException( "layer: " + name + " can't be " + "delievered in SRS: "
    515                                               + this.request.getSrs() );
    516            }
    517
    518            // check bounding box
    519            try {
    520
    521                Envelope bbox = this.request.getBoundingBox();
    522                Envelope layerBbox = layer.getLatLonBoundingBox();
    523                if ( !this.request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
    524                    // transform the bounding box of the request to EPSG:4326
    525                    GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
    526                    bbox = gt.transform( bbox, this.reqCRS );
    527                }
    528                if ( !bbox.intersects( layerBbox ) ) {
    529                    return false;
    530                }
    531
    532            } catch ( Exception e ) {
    533                LOG.logError( e.getMessage(), e );
    534                throw new OGCWebServiceException( "couldn't compare bounding boxes\n" + e.toString() );
    535            }
    536
    537            return true;
    538        }
    539
    540        /**
    541         * put a theme to the passed index of the themes array. The second param passed is a <tt>Theme</tt> or an exception
    542         *
    543         * @param index
    544         * @param o
    545         */
    546        protected synchronized void putTheme( int index, Object o ) {
    547            this.themes[index] = o;
    548        }
    549
    550        /**
    551         * renders the map from the <tt>DisplayElement</tt>s
    552         */
    553        private void renderMap() {
    554
    555            // GetMapResult response = null;
    556            OGCWebServiceException exce = null;
    557
    558            ArrayList<Object> list = new ArrayList<Object>( 50 );
    559            for ( int i = 0; i < this.themes.length; i++ ) {
    560                if ( this.themes[i] instanceof Exception ) {
    561                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", this.themes[i].toString() );
    562                }
    563                if ( this.themes[i] instanceof OGCWebServiceException ) {
    564                    exce = (OGCWebServiceException) this.themes[i];
    565                    break;
    566                }
    567                if ( this.themes[i] != null ) {
    568                    list.add( this.themes[i] );
    569                }
    570            }
    571
    572            if ( exce == null ) {
    573                // only if no exception occured
    574                try {
    575                    Theme[] th = list.toArray( new Theme[list.size()] );
    576                    MapView map = null;
    577                    if ( th.length > 0 ) {
    578                        double tr = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
    579                        tr = WMPSConfiguration.INCH2M / tr;
    580                        map = MapFactory.createMapView( "deegree WMS", this.request.getBoundingBox(), this.reqCRS, th, tr );
    581                    }
    582                    this.graph.setClip( 0, 0, this.request.getWidth(), this.request.getHeight() );
    583                    if ( !this.request.getTransparency() ) {
    584                        this.graph.setColor( this.request.getBGColor() );
    585                        this.graph.fillRect( 0, 0, this.request.getWidth(), this.request.getHeight() );
    586                    }
    587                    if ( map != null ) {
    588                        Theme[] allthemes = map.getAllThemes();
    589                        map.addOptimizer( new LabelOptimizer( allthemes ) );
    590                        // antialiasing must be switched of for gif output format
    591                        // because the antialiasing may create more than 255 colors
    592                        // in the map/image, even just a few colors are defined in
    593                        // the styles
    594                        if ( !this.configuration.getDeegreeParams().isAntiAliased() ) {
    595                            ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
    596                                                                          RenderingHints.VALUE_ANTIALIAS_ON );
    597                            ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
    598                                                                          RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
    599                        }
    600                        map.paint( this.graph );
    601                    }
    602                } catch ( Exception e ) {
    603                    LOG.logError( e.getMessage(), e );
    604                    exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
    605                }
    606            }
    607
    608            // print a copyright note at the left lower corner of the map
    609            printCopyright( this.graph, this.request.getHeight() );
    610
    611        }
    612
    613        /**
    614         * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
    615         * capabilities/configuration
    616         *
    617         * @param g
    618         *            graphic context of the map
    619         * @param heigth
    620         *            height of the map in pixel
    621         */
    622        private void printCopyright( Graphics g, int heigth ) {
    623
    624            WMPSDeegreeParams dp = this.configuration.getDeegreeParams();
    625            String copyright = dp.getCopyright();
    626            if ( this.copyrightImg != null ) {
    627                g.drawImage( this.copyrightImg, 8, heigth - this.copyrightImg.getHeight() - 5, null );
    628            } else {
    629                if ( copyright != null ) {
    630                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
    631                    g.setColor( Color.BLACK );
    632                    g.drawString( copyright, 8, heigth - 15 );
    633                    g.drawString( copyright, 10, heigth - 15 );
    634                    g.drawString( copyright, 8, heigth - 13 );
    635                    g.drawString( copyright, 10, heigth - 13 );
    636                    g.setColor( Color.WHITE );
    637                    g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
    638                    g.drawString( copyright, 9, heigth - 14 );
    639                    // g.dispose();
    640                }
    641            }
    642        }
    643    }