001    //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wmps/PrintMapHandler.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    
037    package org.deegree.ogcwebservices.wmps;
038    
039    import java.awt.Color;
040    import java.awt.Graphics;
041    import java.awt.Graphics2D;
042    import java.awt.Image;
043    import java.awt.geom.AffineTransform;
044    import java.awt.image.AffineTransformOp;
045    import java.awt.image.BufferedImage;
046    import java.io.ByteArrayOutputStream;
047    import java.io.File;
048    import java.io.FileOutputStream;
049    import java.io.IOException;
050    import java.io.InputStream;
051    import java.io.OutputStream;
052    import java.io.StringReader;
053    import java.net.MalformedURLException;
054    import java.net.URI;
055    import java.net.URISyntaxException;
056    import java.net.URL;
057    import java.sql.Connection;
058    import java.sql.SQLException;
059    import java.text.MessageFormat;
060    import java.util.ArrayList;
061    import java.util.HashMap;
062    import java.util.List;
063    import java.util.Map;
064    
065    import net.sf.jasperreports.engine.JREmptyDataSource;
066    import net.sf.jasperreports.engine.JRException;
067    import net.sf.jasperreports.engine.JasperExportManager;
068    import net.sf.jasperreports.engine.JasperFillManager;
069    import net.sf.jasperreports.engine.JasperPrint;
070    import net.sf.jasperreports.engine.JasperPrintManager;
071    
072    import org.deegree.datatypes.values.Values;
073    import org.deegree.framework.log.ILogger;
074    import org.deegree.framework.log.LoggerFactory;
075    import org.deegree.framework.util.ImageUtils;
076    import org.deegree.framework.util.MapUtils;
077    import org.deegree.framework.util.StringTools;
078    import org.deegree.framework.xml.NamespaceContext;
079    import org.deegree.framework.xml.XMLFragment;
080    import org.deegree.framework.xml.XMLParsingException;
081    import org.deegree.framework.xml.XMLTools;
082    import org.deegree.graphics.sld.StyledLayerDescriptor;
083    import org.deegree.graphics.transformation.WorldToScreenTransform;
084    import org.deegree.i18n.Messages;
085    import org.deegree.model.coverage.grid.WorldFile;
086    import org.deegree.model.crs.CRSFactory;
087    import org.deegree.model.crs.CoordinateSystem;
088    import org.deegree.model.crs.UnknownCRSException;
089    import org.deegree.model.spatialschema.Envelope;
090    import org.deegree.model.spatialschema.GeometryFactory;
091    import org.deegree.model.spatialschema.Point;
092    import org.deegree.ogcbase.CommonNamespaces;
093    import org.deegree.ogcwebservices.InconsistentRequestException;
094    import org.deegree.ogcwebservices.OGCWebServiceException;
095    import org.deegree.ogcwebservices.wmps.configuration.PrintMapParam;
096    import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration;
097    import org.deegree.ogcwebservices.wmps.operation.PrintMap;
098    import org.deegree.ogcwebservices.wmps.operation.PrintMapResponseDocument;
099    import org.deegree.ogcwebservices.wmps.operation.TextArea;
100    import org.deegree.ogcwebservices.wms.capabilities.Layer;
101    import org.deegree.ogcwebservices.wms.capabilities.LegendURL;
102    import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
103    import org.deegree.ogcwebservices.wms.capabilities.Style;
104    import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
105    import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource;
106    import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource;
107    import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource;
108    import org.deegree.ogcwebservices.wms.operation.GetMap;
109    import org.w3c.dom.Document;
110    import org.w3c.dom.Element;
111    import org.w3c.dom.Node;
112    
113    /**
114     * Handles the PrintMap request. Retrieves the request from the DB and creates a pdf file.
115     * 
116     * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a>
117     * @author last edited by: $Author: apoth $
118     * 
119     * @version 2.0, $Revision: 21191 $, $Date: 2009-12-02 19:41:35 +0100 (Mi, 02 Dez 2009) $
120     */
121    public class PrintMapHandler implements Runnable {
122    
123        private static final ILogger LOG = LoggerFactory.getLogger( PrintMapHandler.class );
124    
125        private final double TILE_MAX_SIZE = 800;
126    
127        private final String FORMAT = ".png";
128    
129        private final String MIMETYPE = "image/png";
130    
131        private final String EXCEPTION = "application/vnd.ogc.se_inimage";
132    
133        private WMPSConfiguration configuration;
134    
135        private String message;
136    
137        /**
138         * Creates a new instance of the PrintMapHandler for the current configuration.
139         * 
140         * @param configuration
141         */
142        public PrintMapHandler( WMPSConfiguration configuration ) {
143            this.configuration = configuration;
144        }
145    
146        /**
147         * Run a new thread for each PrintMap request. This Thread runs till no more PrintMap requests are available in the
148         * DB.
149         * 
150         * @see java.lang.Runnable#run()
151         */
152        public void run() {
153    
154            RequestManager manager = null;
155            PrintMapResponseDocument response = null;
156            WMPSDatabase wmpsDB = null;
157            Connection connection = null;
158            try {
159                wmpsDB = new WMPSDatabase( this.configuration.getDeegreeParams().getCacheDatabase() );
160    
161                while ( true ) {
162                    connection = wmpsDB.acquireConnection();
163                    // get request from DB
164                    PrintMap printMap = wmpsDB.selectPrintMapRequest( connection );
165                    if ( printMap == null ) {
166                        break;
167                    }
168    
169                    try {
170                        LOG.logDebug( "Performing print map" );
171                        manager = new DefaultRequestManager( this.configuration, printMap );
172                        performPrintMap( printMap, false );
173                        wmpsDB.updateDB( connection, printMap.getId(), printMap.getTimestamp(), "TRUE" );
174                        response = manager.createFinalResponse( this.message, null );
175                        manager.sendEmail( response );
176                    } catch ( Exception e ) {
177                        LOG.logError( e.getMessage(), e );
178                        wmpsDB.updateDB( connection, printMap.getId(), printMap.getTimestamp(), "FAILED" );
179                        sendExceptionMail( manager, response, e );
180                    } finally {
181                        releaseConnection( wmpsDB, connection );
182                    }
183                    LOG.logDebug( "Done performing PrintMap request." );
184                }
185            } catch ( Exception e ) {
186                LOG.logError( e.getMessage(), e );
187                sendExceptionMail( manager, response, e );
188            } finally {
189                releaseConnection( wmpsDB, connection );
190            }
191        }
192    
193        private void sendExceptionMail( RequestManager manager, PrintMapResponseDocument response, Exception e ) {
194            if ( manager != null ) {
195                try {
196                    response = manager.createFinalResponse( "print map failed", e.getMessage() );
197                    manager.sendEmail( response );
198                } catch ( Exception e1 ) {
199                    // should just happen if mail server is not reachable
200                    // in this case the error just can be logged
201                    XMLFragment doc = new XMLFragment( response.getRootElement() );
202                    LOG.logDebug( doc.getAsString() );
203                    LOG.logError( e.getMessage(), e );
204                }
205            }
206        }
207    
208        private void releaseConnection( WMPSDatabase wmpsDB, Connection connection ) {
209            try {
210                if ( !connection.isClosed() ) {
211                    wmpsDB.releaseConnection( connection );
212                }
213            } catch ( SQLException e ) {
214                // should never happen
215                LOG.logError( e.getMessage(), e );
216            }
217        }
218    
219        /**
220         * performs a synchronous printMap processing
221         * 
222         * @param printMap
223         * @return byte[]
224         * @throws Exception
225         */
226        public byte[] runSynchronous( PrintMap printMap )
227                                throws Exception {
228            return performPrintMap( printMap, true );
229        }
230    
231        /**
232         * From each PrintMap request run the WMS GetMap request.
233         * 
234         * @param printMap
235         * @param synchronous
236         * @return byte[]
237         * @throws PrintMapServiceException
238         * @throws IOException
239         */
240        private byte[] performPrintMap( PrintMap printMap, boolean synchronous )
241                                throws PrintMapServiceException, IOException {
242    
243            Map<String, Layer> config_layers = retrieveLayersFromConfig( printMap );
244    
245            int[] mapParams = getMapParamsFromTemplate( printMap );
246            int scaleDenominator = printMap.getScaleDenominator();
247            Envelope bbox = printMap.getBBOX();
248            if ( bbox == null ) {
249                LOG.logDebug( "BBOX not defined. Using the center and scale to calculate a new BBOX." );
250                Point center = printMap.getCenter();
251                bbox = createBBOX( printMap, center, scaleDenominator, mapParams[0], mapParams[1] );
252            } else { // Adjust aspect ratio of bbox to aspect ratio of template map area
253                double bboxAspectRatio = bbox.getWidth() / bbox.getHeight();
254                double printAspectRatio = ( (double) mapParams[0] ) / mapParams[1];
255                if ( bboxAspectRatio > printAspectRatio ) {
256                    double centerY = bbox.getMin().getY() + ( ( bbox.getMax().getY() - bbox.getMin().getY() ) / 2d );
257                    double height = bbox.getWidth() * ( 1.0 / printAspectRatio );
258                    double minY = centerY - ( height / 2d );
259                    double maxY = centerY + ( height / 2d );
260                    bbox = GeometryFactory.createEnvelope( bbox.getMin().getX(), minY, bbox.getMax().getX(), maxY,
261                                                           bbox.getCoordinateSystem() );
262                } else {
263                    double centerX = bbox.getMin().getX() + ( ( bbox.getMax().getX() - bbox.getMin().getX() ) / 2d );
264                    double width = bbox.getHeight() * printAspectRatio;
265                    double minX = centerX - ( width / 2d );
266                    double maxX = centerX + ( width / 2d );
267                    bbox = GeometryFactory.createEnvelope( minX, bbox.getMin().getY(), maxX, bbox.getMax().getY(),
268                                                           bbox.getCoordinateSystem() );
269                }
270            }
271            if ( scaleDenominator == -1 ) {
272                LOG.logDebug( "Scale not defined. Using MapUtil to calculate the scale denominator for the current bbox and map sizes" );
273                double pixelSize = WMPSConfiguration.INCH2M
274                                   / configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
275                scaleDenominator = (int) MapUtils.calcScale( mapParams[0], mapParams[1], bbox, bbox.getCoordinateSystem(),
276                                                             pixelSize );
277                LOG.logDebug( "calculated map scale denominator: ", scaleDenominator );
278            }
279            BufferedImage mapImage = null;
280            try {
281                mapImage = performBuildMapImage( config_layers, printMap, bbox, mapParams[0], mapParams[1] );
282                saveImageToDisk( printMap, mapImage, "MAP", bbox );
283                String s = StringTools.concat( 100, "Retrieved PrintMap request '", printMap.getId(), "' and saved to disk" );
284                LOG.logDebug( s );
285            } catch ( OGCWebServiceException e ) {
286                LOG.logError( e.getMessage(), e );
287                throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_PERFORMING_PRINTMAP" ) );
288            } catch ( IOException e ) {
289                LOG.logError( e.getMessage(), e );
290                throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_WRITING_MAP_TMP_FILE" ) );
291            }
292            if ( printMap.getLegend() ) {
293                try {
294                    BufferedImage legendImage = performGetLegend( config_layers, printMap, mapParams );
295                    saveImageToDisk( printMap, legendImage, "LEGEND", null );
296                    LOG.logDebug( "Saved the legend image file to disk." );
297                } catch ( IOException e ) {
298                    LOG.logError( e.getMessage(), e );
299                    throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_WRITING_LEGEND_TMP_FILE" ) );
300                }
301            }
302            return exportOutput( printMap, scaleDenominator, synchronous );
303        }
304    
305        /**
306         * Build the Map image for the current PrintMap request. Vector and Raster layers are handled seperately.
307         * 
308         * @param config_layers
309         * @param printMap
310         * @param bbox
311         * @param width
312         * @param height
313         * @return BufferedImage
314         * @throws OGCWebServiceException
315         */
316        private BufferedImage performBuildMapImage( Map<String, Layer> config_layers, PrintMap printMap, Envelope bbox,
317                                                    double width, double height )
318                                throws OGCWebServiceException {
319    
320            BufferedImage targetImage = new BufferedImage( (int) width, (int) height, BufferedImage.TYPE_INT_ARGB );
321            Graphics2D g = (Graphics2D) targetImage.getGraphics();
322    
323            if ( !printMap.getTransparent() ) {
324                g.setBackground( printMap.getBGColor() );
325            }
326    
327            handleLayerDatasources( config_layers, printMap, bbox, width, height, g );
328            g.dispose();
329    
330            return targetImage;
331        }
332    
333        /**
334         * Determines the datasource for each layer(vector, raster).
335         * 
336         * @param config_layers
337         * @param printMap
338         * @param bbox
339         * @param width
340         * @param height
341         * @param g
342         * @throws OGCWebServiceException
343         * @throws InconsistentRequestException
344         */
345        private void handleLayerDatasources( Map<String, Layer> config_layers, PrintMap printMap, Envelope bbox,
346                                             double width, double height, Graphics g )
347                                throws OGCWebServiceException, InconsistentRequestException {
348    
349            int dpi = printMap.getDpi();
350            if ( dpi < 0 ) {
351                dpi = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
352            }
353            double px = WMPSConfiguration.INCH2M / dpi;
354            CoordinateSystem crs;
355            try {
356                crs = CRSFactory.create( printMap.getSRS() );
357            } catch ( UnknownCRSException e1 ) {
358                throw new InconsistentRequestException( e1.getMessage() );
359            }
360            double scale = MapUtils.calcScale( (int) width, (int) height, bbox, crs, px );
361            GetMap.Layer[] printMapLayers = printMap.getLayers();
362            for ( int i = 0; i < printMapLayers.length; i++ ) {
363                String name = printMapLayers[i].getName();
364    
365                Layer configLayer = config_layers.get( name );
366                ScaleHint scaleHint = configLayer.getScaleHint();
367                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
368                    LOG.logDebug( "current scale", scale );
369                    LOG.logDebug( "valid layer min scale", scaleHint.getMin() );
370                    LOG.logDebug( "valid layer max scale", scaleHint.getMax() );
371                }
372                if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax() ) {
373                    String type = determineDatasourceType( configLayer, scale );
374                    if ( type != null && "vector".equalsIgnoreCase( type ) ) {
375    
376                        GetMap.Layer[] lay = null;
377                        if ( ( printMapLayers.length - i ) > 1 ) {
378                            lay = getContinuousVectorLayer( config_layers, printMapLayers, scale, i );
379                        } else {
380                            lay = new GetMap.Layer[] { printMapLayers[i] };
381                        }
382                        try {
383                            for ( int j = 0; j < lay.length; j++ ) {
384                                LOG.logDebug( "handle as vector layer: ", lay[j].getName() );
385                            }
386                            handleVectorDataLayer( printMap, bbox, width, height, g, lay );
387                        } catch ( OGCWebServiceException e ) {
388                            LOG.logError( e.getMessage(), e );
389                            throw new OGCWebServiceException( Messages.getMessage( "WMPS_ERROR_HANDLING_GETMAP", name ) );
390                        }
391                        // Skip the number of layers already handled.
392                        if ( lay.length != 1 ) {
393                            i = i + ( lay.length - 1 );
394                        }
395                    } else {
396                        // must be a raster data layer
397                        GetMap.Layer[] lay = new GetMap.Layer[] { printMapLayers[i] };
398                        LOG.logDebug( "handle as raster layer: ", printMapLayers[i].getName() );
399                        try {
400                            handleRasterDataLayer( printMap, bbox, width, height, g, lay );
401                        } catch ( OGCWebServiceException e ) {
402                            LOG.logError( e.getMessage(), e );
403                            throw new OGCWebServiceException( Messages.getMessage( "WMPS_ERROR_HANDLING_GETMAP", name ) );
404                        }
405                    }
406                } else {
407                    String s = StringTools.concat( 100, "No Datasource available for layer: ", name, " at scale: ", scale );
408                    LOG.logInfo( s );
409                }
410            }
411    
412        }
413    
414        /**
415         * returns an array of layers that:
416         * <ul>
417         * <li>a) made of vector data
418         * <li>b) are continous in the requested list of layers
419         * </ul>
420         * 
421         * @param config_layers
422         * @param printMapLayers
423         * @param mapScale
424         *            scale of the entire map
425         * @param i
426         * @return Layer[]
427         */
428        private GetMap.Layer[] getContinuousVectorLayer( Map<String, Layer> config_layers, GetMap.Layer[] printMapLayers,
429                                                         double mapScale, int i ) {
430    
431            List<GetMap.Layer> layers = new ArrayList<GetMap.Layer>( printMapLayers.length );
432            int counter = 0;
433            for ( ; i < printMapLayers.length; i++ ) {
434                String name = printMapLayers[i].getName();
435                Layer configLayer = config_layers.get( name );
436                String type = determineDatasourceType( configLayer, mapScale );
437                if ( "vector".equals( type ) ) {
438                    layers.add( counter, printMapLayers[i] );
439                    counter++;
440                } else {
441                    break;
442                }
443            }
444    
445            return layers.toArray( new GetMap.Layer[layers.size()] );
446        }
447    
448        /**
449         * Perform the GetMap request for vector layers.
450         * 
451         * @param printMap
452         * @param bbox
453         * @param width
454         * @param height
455         * @param g
456         * @param lay
457         * @throws OGCWebServiceException
458         */
459        private void handleVectorDataLayer( PrintMap printMap, Envelope bbox, double width, double height, Graphics g,
460                                            GetMap.Layer[] lay )
461                                throws OGCWebServiceException {
462    
463            URL sldURL = null;
464            StyledLayerDescriptor sld = null;
465    
466            GetMap getMap = GetMap.create( printMap.getVersion(), printMap.getId(), lay, null, null, this.MIMETYPE,
467                                           (int) width, (int) height, printMap.getSRS(), bbox, printMap.getTransparent(),
468                                           printMap.getBGColor(), this.EXCEPTION, null, sldURL, sld,
469                                           printMap.getVendorSpecificParameters() );
470            DefaultGetMapHandler gmh = new DefaultGetMapHandler( this.configuration, getMap );
471            gmh.performGetMap( printMap, g );
472    
473        }
474    
475        /**
476         * Perform the GetMap request for each raster layer. Here the raster layer is divided into tiles for memory handling
477         * efficiency.
478         * 
479         * @param printMap
480         * @param bbox
481         * @param width
482         * @param height
483         * @param g
484         * @param lay
485         * @throws OGCWebServiceException
486         */
487        private void handleRasterDataLayer( PrintMap printMap, Envelope bbox, double width, double height, Graphics g,
488                                            GetMap.Layer[] lay )
489                                throws OGCWebServiceException {
490    
491            // Get Map (missing) parameters.
492            Values elevation = null;
493            Map<String, Values> sampleDimension = null;
494            Values time = null;
495            URL sldURL = null;
496            StyledLayerDescriptor sld = null;
497    
498            boolean xRemainder = false;
499            int wtx = (int) width % (int) this.TILE_MAX_SIZE;
500            int nkx = (int) width / (int) this.TILE_MAX_SIZE;
501            if ( wtx > 0 ) {
502                xRemainder = true;
503                nkx++;
504            }
505    
506            boolean yRemainder = false;
507            int wty = (int) height % (int) this.TILE_MAX_SIZE;
508            int nky = (int) height / (int) this.TILE_MAX_SIZE;
509            if ( wty > 0 ) {
510                yRemainder = true;
511                nky++;
512            }
513    
514            WorldToScreenTransform trans = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(),
515                                                                       bbox.getMax().getX(), bbox.getMax().getY(), 0d, 0d,
516                                                                       width - 1, height - 1 );
517    
518            for ( int x = 0; x < nkx; x++ ) {
519                double tileWidth = this.TILE_MAX_SIZE;
520                if ( xRemainder ) {
521                    if ( x == nkx - 1 ) {
522                        tileWidth = wtx;
523                    }
524                }
525                for ( int y = 0; y < nky; y++ ) {
526                    double tileHeight = this.TILE_MAX_SIZE;
527                    if ( yRemainder ) {
528                        if ( y == nky - 1 ) {
529                            tileHeight = wty;
530                        }
531                    }
532                    BufferedImage bi = new BufferedImage( (int) tileWidth, (int) tileHeight, BufferedImage.TYPE_INT_ARGB );
533                    Graphics tileg = bi.getGraphics();
534                    // calc bbox
535                    Envelope bb = calculateTileBBOX( trans, x, y, tileWidth, tileHeight, bbox.getCoordinateSystem() );
536                    // create GetMap
537                    GetMap getMap = GetMap.create( printMap.getVersion(), printMap.getId(), lay, elevation,
538                                                   sampleDimension, this.MIMETYPE, (int) tileWidth, (int) tileHeight,
539                                                   printMap.getSRS(), bb, printMap.getTransparent(), printMap.getBGColor(),
540                                                   this.EXCEPTION, time, sldURL, sld,
541                                                   printMap.getVendorSpecificParameters() );
542    
543                    // performGetMap( tileg );
544                    DefaultGetMapHandler gmh = new DefaultGetMapHandler( this.configuration, getMap );
545                    gmh.performGetMap( printMap, tileg );
546                    tileg.dispose();
547                    g.drawImage( bi, (int) Math.round( x * this.TILE_MAX_SIZE ),
548                                 (int) Math.round( y * this.TILE_MAX_SIZE ), (int) Math.round( tileWidth ),
549                                 (int) Math.round( tileHeight + 1 ), null );
550                }
551            }
552    
553        }
554    
555        /**
556         * Calculate the tile BBOX for the raster datalayer.
557         * 
558         * @param trans
559         * @param x
560         * @param y
561         * @param tileWidth
562         * @param tileHeight
563         * @param crs
564         * @return Envelope
565         */
566        private Envelope calculateTileBBOX( WorldToScreenTransform trans, int x, int y, double tileWidth,
567                                            double tileHeight, CoordinateSystem crs ) {
568    
569            double x1 = x * this.TILE_MAX_SIZE;
570            double y1 = y * this.TILE_MAX_SIZE;
571            double x2 = x1 + tileWidth;
572            double y2 = y1 + tileHeight;
573    
574            double minX = trans.getSourceX( x1 );
575            double maxX = trans.getSourceX( x2 );
576            double minY = trans.getSourceY( y2 );
577            double maxY = trans.getSourceY( y1 );
578    
579            return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs );
580        }
581    
582        /**
583         * Parses the Layer datastores to determine the type of the layer. Layers having a vector datasource as well as a
584         * raster datasource for the passed mapScale will be treated as raster layers
585         * 
586         * @param layer
587         * @param mapScale
588         *            scale of the entire map
589         * @return String either raster, vector or nodatasource
590         */
591        private String determineDatasourceType( Layer layer, double mapScale ) {
592    
593            AbstractDataSource[] ads = layer.getDataSource();
594            String type = null;
595    
596            boolean[] mixed = new boolean[] { false, false };
597            for ( int i = 0; i < ads.length; i++ ) {
598                ScaleHint scaleHint = ads[i].getScaleHint();
599                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
600                    LOG.logDebug( "mapscale", mapScale );
601                    LOG.logDebug( "valid datasource min scale", scaleHint.getMin() );
602                    LOG.logDebug( "valid datasource max scale", scaleHint.getMax() );
603                }
604                if ( mapScale >= scaleHint.getMin() && mapScale < scaleHint.getMax() ) {
605                    if ( ( ads[i] instanceof RemoteWMSDataSource ) || ( ads[i] instanceof RemoteWCSDataSource )
606                         || ( ads[i] instanceof LocalWCSDataSource ) ) {
607                        type = "raster";
608                        mixed[0] = true;
609                    } else {
610                        type = "vector";
611                        mixed[1] = true;
612                    }
613                }
614            }
615            if ( mixed[0] && mixed[1] ) {
616                // Layers having a vector datasource as well as a raster datasource
617                // for the passed mapScale will be treated as raster layers
618                type = "raster";
619            }
620    
621            return type;
622        }
623    
624        /**
625         * Retrieve the legend images from the URL given in the configuration layers.
626         * 
627         * 
628         * @param layerDefs
629         * @param printMap
630         * @param mapParams
631         * @return BufferedImage
632         * @throws OGCWebServiceException
633         * @throws InconsistentRequestException
634         */
635        private BufferedImage performGetLegend( Map<String, Layer> layerDefs, PrintMap printMap, int[] mapParams ) {
636    
637            GetMap.Layer[] layers = printMap.getLayers();
638    
639            Map<String, Image> legendImg = new HashMap<String, Image>( layers.length );
640            int height = 0;
641            int maxWidth = 0;
642            for ( int i = 0; i < layers.length; i++ ) {
643                String name = layers[i].getName();
644                String styleName = layers[i].getStyleName();
645    
646                Layer configLayer = layerDefs.get( name );
647                Style style = null;
648                if ( "$DEFAULT".equals( styleName ) ) {
649                    style = configLayer.getStyleResource( "default:" + name );
650                } else {
651                    style = configLayer.getStyleResource( styleName );
652                }
653                LegendURL[] lu = style.getLegendURL();
654                if ( lu != null && lu.length > 0 ) {
655                    int k = 0;
656                    boolean drawn = false;
657                    while ( k < lu.length && !drawn ) {
658                        URL url = lu[k++].getOnlineResource();
659                        try {
660                            Image img = ImageUtils.loadImage( url );
661                            legendImg.put( name, img );
662                            drawn = true;
663                        } catch ( IOException e ) {
664                            // we do not throw the exception bacause there are maybe
665                            // further URLs we can try and even if not the user will
666                            // be informed by a special legend symbol that no correct
667                            // symbol can be accessed
668                            LOG.logError( "can not access LegendURL: " + url, e );
669                        } catch ( Exception e ) {
670                            LOG.logError( "can not read image from LegendURL: " + url, e );
671                        }
672                    }
673                    if ( !drawn ) {
674                        // if legend URL(s) are defined but none of them can
675                        // be accessed
676                        String s = StringTools.concat( 100, "no legend URL accessable for layer: ", name, "; style: ",
677                                                       styleName, " using dummy legend image" );
678                        LOG.logError( s );
679                        BufferedImage img = drawMissingLegendURLImage( s );
680                        legendImg.put( name, img );
681                    }
682                } else {
683                    // if no legend URL has been defined which probably is the case
684                    // for WMS no supporting GetLegendGraphic operation
685                    String s = StringTools.concat( 100, "no legend URL available for layer: ", name, "; style: ",
686                                                   styleName, " using dummy legend image" );
687                    LOG.logError( s );
688                    BufferedImage img = drawMissingLegendURLImage( s );
689                    legendImg.put( name, img );
690                }
691                // update all over legend height and width
692                BufferedImage img = (BufferedImage) legendImg.get( name );
693                if ( img.getWidth() > maxWidth ) {
694                    maxWidth = img.getWidth();
695                }
696                height += img.getHeight();
697            }
698    
699            // depending on the size of the legend all legend symbols must scaled by
700            // the same factor to fit the legend size defined in the current template
701            double dh = calcDeltaLegend( mapParams[2], mapParams[3], height, maxWidth );
702    
703            // create an empty basic image as target for painting all legend symbols
704            BufferedImage actualLegendImage = null;
705            if ( mapParams[2] > 0 && mapParams[3] > 0 ) {
706                actualLegendImage = new BufferedImage( mapParams[2], mapParams[3], BufferedImage.TYPE_INT_ARGB );
707    
708                Graphics2D g = (Graphics2D) actualLegendImage.getGraphics();
709    
710                int y = 0;
711                for ( int i = layers.length; i > 0; i-- ) {
712                    // draw all legend symbols in correct order
713                    String name = layers[i - 1].getName();
714                    BufferedImage img = scaleImage( (BufferedImage) legendImg.get( name ), dh );
715                    g.drawImage( img, 0, y, null );
716                    y += img.getHeight();
717                }
718    
719                g.dispose();
720            } else {
721                // create empty legend image if size is not valid
722                actualLegendImage = new BufferedImage( 10, 10, BufferedImage.TYPE_INT_ARGB );
723            }
724    
725            return actualLegendImage;
726        }
727    
728        private BufferedImage drawMissingLegendURLImage( String text ) {
729            BufferedImage img = new BufferedImage( 550, 50, BufferedImage.TYPE_INT_ARGB );
730            Graphics g = img.getGraphics();
731            g.setColor( Color.YELLOW );
732            g.fillRect( 0, 0, img.getWidth(), img.getHeight() );
733            g.setColor( Color.RED );
734            g.drawString( text, 10, 20 );
735            g.dispose();
736            return img;
737        }
738    
739        /**
740         * calculates factor for resizing legend images
741         * 
742         * @param legendWidth
743         *            The width of the legend area
744         * @param legendHeight
745         *            The height of the legend area
746         * @param height
747         *            The height of all legends put together
748         * @param maxWidth
749         *            The width of the wides legend
750         * @return Returns the factor for resizing legend images
751         */
752        private double calcDeltaLegend( int legendWidth, int legendHeight, int height, int maxWidth ) {
753            double dh = legendHeight / (double) height;
754            double dw = legendWidth / (double) maxWidth;
755            if ( dw < dh ) {
756                return dw;
757            }
758            return dh;
759        }
760    
761        /**
762         * Scale Image.
763         * 
764         * @param image
765         * @param ratio
766         * @return BufferedImage
767         */
768        private BufferedImage scaleImage( BufferedImage image, double ratio ) {
769    
770            AffineTransform tx = new AffineTransform();
771            tx.scale( ratio, ratio );
772            AffineTransformOp op = new AffineTransformOp( tx, AffineTransformOp.TYPE_BILINEAR );
773    
774            return op.filter( image, null );
775        }
776    
777        /**
778         * Save the GetMap image to the disk.
779         * 
780         * @param printMap
781         * @param image
782         * @param type
783         * @param bbox
784         * @throws IOException
785         */
786        private void saveImageToDisk( PrintMap printMap, BufferedImage image, String type, Envelope bbox )
787                                throws IOException {
788    
789            PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam();
790            String fileName = null;
791            String templateName = printMap.getTemplate();
792            if ( type.equalsIgnoreCase( "MAP" ) ) {
793                fileName = StringTools.concat( 200, "Map_", templateName, '_', printMap.getId(), this.FORMAT );
794                WorldFile wf = new WorldFile( bbox.getWidth() / image.getWidth(), bbox.getHeight() / image.getHeight(), 0,
795                                              0, bbox );
796                String wffn = StringTools.concat( 200, "Map_", templateName, '_', printMap.getId(), ".wld" );
797                URL url = new URL( printMapParam.getPlotImageDir() + '/' + wffn );
798                OutputStream os = null;
799                try {
800                    os = new FileOutputStream( new File( url.toURI() ) );
801                } catch ( URISyntaxException e ) {
802                    // should never happen because each valid URL is a valid URI too
803                    LOG.logError( e.getMessage(), e );
804                }
805                WorldFile.writeWorldFile( os, wf );
806            } else if ( type.equalsIgnoreCase( "LEGEND" ) ) {
807                fileName = StringTools.concat( 200, "Legend_", templateName, '_', printMap.getId(), this.FORMAT );
808            }
809    
810            String path = printMapParam.getPlotImageDir() + '/' + fileName;
811            URL downloadDirectory = new URL( path );
812    
813            try {
814                ImageUtils.saveImage( image, new File( downloadDirectory.toURI() ), 1 );
815            } catch ( URISyntaxException e ) {
816                // should never happen because each valid URL is a valid URI too
817                LOG.logError( e.getMessage(), e );
818            }
819    
820        }
821    
822        /**
823         * Use JasperReports to create a pdf file. The Jasper Template will be loaded and a dynamic link will be created to
824         * the image on the disk.
825         * 
826         * @param printMap
827         * @param scaleDenominator
828         * @param synchronous
829         * @return byte[]
830         * @throws PrintMapServiceException
831         */
832        private byte[] exportOutput( PrintMap printMap, int scaleDenominator, boolean synchronous )
833                                throws PrintMapServiceException {
834    
835            // generate a file using JasperReports.
836            byte[] b = null;
837            try {
838                JasperPrint print = fillJasperTemplate( printMap, scaleDenominator );
839                b = doJasperPrintExport( printMap, print, synchronous );
840            } catch ( Exception e ) {
841                LOG.logError( e.getMessage(), e );
842                throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_SAVING_PDF" ) );
843            }
844    
845            return b;
846        }
847    
848        /**
849         * Open the JasperAPI to process the PrintMap request.
850         * 
851         * @param printMap
852         * @param scaleDenominator
853         * @return JasperPrint
854         * @throws InconsistentRequestException
855         * @throws JRException
856         * @throws MalformedURLException
857         */
858        private JasperPrint fillJasperTemplate( PrintMap printMap, int scaleDenominator )
859                                throws JRException, MalformedURLException {
860    
861            URL templatePath = null;
862            try {
863                String templateName = printMap.getTemplate();
864                templatePath = getTemplatePath( templateName, true );
865            } catch ( IOException e ) {
866                LOG.logError( e.getMessage(), e );
867                throw new MalformedURLException( Messages.getMessage( "WMPS_ERROR_CREATING_TEMPLATEPATH",
868                                                                      printMap.getTemplate() ) );
869            }
870    
871            Map<String, Object> parameters = new HashMap<String, Object>();
872            URL mapImagePath = getResultImagePath( printMap, "Map" );
873            parameters.put( "MAP", mapImagePath.getFile() );
874            if ( printMap.getLegend() ) {
875                URL legendImagePath = getResultImagePath( printMap, "Legend" );
876                parameters.put( "LEGEND", legendImagePath.getFile() );
877            }
878    
879            String scale = "1:" + scaleDenominator;
880    
881            if ( printMap.getScaleBar() == true ) {
882                parameters.put( "SCALE", scale );
883            }
884    
885            TextArea[] textAreas = printMap.getTextAreas();
886            if ( textAreas != null && textAreas.length > 0 ) {
887                for ( int i = 0; i < textAreas.length; i++ ) {
888                    TextArea textArea = textAreas[i];
889                    LOG.logDebug( "Names and text fields entered, extracted." );
890                    String name = textArea.getName();
891                    String text = textArea.getText();
892                    if ( name != null ) {
893                        LOG.logDebug( "If name is not null, allocate it to the hashmap 'parameters' in uppercase." );
894                        parameters.put( name.toUpperCase(), text );
895                        parameters.put( name, text );
896                    }
897                }
898            }
899            String title = printMap.getTitle();
900            if ( title != null ) {
901                parameters.put( "TITLE", title );
902            }
903            String copyright = printMap.getCopyright();
904            if ( copyright != null ) {
905                parameters.put( "COPYRIGHT", copyright );
906            }
907            String note = printMap.getNote();
908            if ( note != null ) {
909                parameters.put( "NOTE", note );
910            }
911            LOG.logDebug( "JASPER Parameter: ", parameters );
912            JasperPrint print = null;
913            try {
914                print = JasperFillManager.fillReport( templatePath.getFile(), parameters, new JREmptyDataSource() );
915            } catch ( JRException e ) {
916                LOG.logError( e.getMessage(), e );
917                throw new JRException( Messages.getMessage( "WMPS_ERROR_BUILDING_TEMPLATE", templatePath ) );
918            }
919    
920            return print;
921        }
922    
923        /**
924         * Retrieve the result map image file path for the current request.
925         * 
926         * @param printMap
927         * @param type
928         * @return URL
929         * @throws MalformedURLException
930         */
931        private URL getResultImagePath( PrintMap printMap, String type )
932                                throws MalformedURLException {
933    
934            String templateName = printMap.getTemplate();
935            String fileName = type + "_" + templateName + "_" + printMap.getId() + this.FORMAT;
936            PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam();
937            String path = printMapParam.getPlotImageDir() + '/' + fileName;
938            URL imagePath = new URL( path );
939    
940            return imagePath;
941    
942        }
943    
944        /**
945         * Print the layer to a the specified format.
946         * 
947         * @param printMap
948         * @param print
949         * @param synchronous
950         * @return byte[]
951         * @throws JRException
952         * @throws IOException
953         */
954        private byte[] doJasperPrintExport( PrintMap printMap, JasperPrint print, boolean synchronous )
955                                throws JRException, IOException {
956    
957            String format = this.configuration.getDeegreeParams().getPrintMapParam().getFormat();
958            String templateName = printMap.getTemplate();
959            String filename = StringTools.concat( 200, format, '_', templateName, '_', printMap.getId(), '.', format );
960            String directory = this.configuration.getDeegreeParams().getPrintMapParam().getPlotDirectory();
961    
962            URL downloadFile = new URL( directory + '/' + filename );
963    
964            byte[] b = null;
965            try {
966                if ( synchronous ) {
967                    b = doSynchronousProcessing( print, format );
968                } else {
969                    doSaveResultDocument( print, format, downloadFile );
970                    createMailLink( filename );
971                }
972            } catch ( JRException e ) {
973                LOG.logError( e.getMessage(), e );
974                throw new JRException( Messages.getMessage( "WMPS_ERROR_PRINTING_REPORT", format, downloadFile.getFile() ) );
975            }
976    
977            return b;
978        }
979    
980        /**
981         * Create a mail link to be sent to the user email address. The mail link allows the user to open the pdf document
982         * for viewing and downloading purposes. Here 2 cases are taken into consideration
983         * <ul>
984         * <li>An authentification servlet link will be sent to the client.
985         * <li>A direct access to the clients data file.
986         * </ul>
987         * 
988         * @param printMap
989         * @param filename
990         */
991        private void createMailLink( String filename ) {
992            PrintMapParam pmp = this.configuration.getDeegreeParams().getPrintMapParam();
993            String onlineResource = pmp.getOnlineResource();
994            String template = pmp.getMailTextTemplate();
995            this.message = MessageFormat.format( template, new Object[] { onlineResource, filename.trim() } );
996        }
997    
998        /**
999         * Save the result document using the JasperExportManager to the file specified.
1000         * 
1001         * @param print
1002         * @param format
1003         * @param downloadDirectory
1004         * @throws JRException
1005         * @throws IOException
1006         */
1007        private void doSaveResultDocument( JasperPrint print, String format, URL downloadDirectory )
1008                                throws JRException, IOException {
1009            if ( format.equalsIgnoreCase( "pdf" ) ) {
1010                LOG.logDebug( "Exporting as pdf to file " + downloadDirectory.getFile() );
1011                JasperExportManager.exportReportToPdfFile( print, downloadDirectory.getFile() );
1012            } else if ( format.equalsIgnoreCase( "html" ) ) {
1013                LOG.logDebug( "Exporting as html to file " + downloadDirectory.getFile() );
1014                JasperExportManager.exportReportToHtmlFile( print, downloadDirectory.getFile() );
1015            } else if ( format.equalsIgnoreCase( "xml" ) ) {
1016                LOG.logDebug( "Exporting as xml to file " + downloadDirectory.getFile() );
1017                JasperExportManager.exportReportToXmlFile( print, downloadDirectory.getFile(), false );
1018            } else if ( format.equalsIgnoreCase( "png" ) ) {
1019                LOG.logDebug( "Exporting as xml to file " + downloadDirectory.getFile() );
1020                Image image = JasperPrintManager.printPageToImage( print, 0, 1 );
1021                ImageUtils.saveImage( (BufferedImage) image, new File( downloadDirectory.getFile() ), 1f );
1022            }
1023    
1024        }
1025    
1026        /**
1027         * Export the result document to a stream to be returend to the waiting client.
1028         * 
1029         * @param print
1030         * @param format
1031         * @return byte[]
1032         * @throws JRException
1033         */
1034        private byte[] doSynchronousProcessing( JasperPrint print, String format )
1035                                throws JRException {
1036    
1037            byte[] b;
1038            ByteArrayOutputStream bos = new ByteArrayOutputStream( 100000 );
1039            if ( format.equalsIgnoreCase( "pdf" ) ) {
1040                JasperExportManager.exportReportToPdfStream( print, bos );
1041            } else if ( format.equalsIgnoreCase( "xml" ) ) {
1042                JasperExportManager.exportReportToXmlStream( print, bos );
1043            }
1044            b = bos.toByteArray();
1045    
1046            return b;
1047        }
1048    
1049        /**
1050         * Retrieve PrintMap request layers from the WMPSConfiguration file. Counter check if the layer has been defined and
1051         * also get the type of datasource used by the layer.
1052         * 
1053         * @param printMap
1054         * @return Map key-> layer name, value -> configLayer
1055         * @throws PrintMapServiceException
1056         */
1057        private Map<String, Layer> retrieveLayersFromConfig( PrintMap printMap )
1058                                throws PrintMapServiceException {
1059    
1060            GetMap.Layer[] requestedLayers = printMap.getLayers();
1061            Map<String, Layer> layers = new HashMap<String, Layer>();
1062            for ( int i = 0; i < requestedLayers.length; i++ ) {
1063                GetMap.Layer layer = requestedLayers[i];
1064    
1065                Layer configLayer = this.configuration.getLayer( layer.getName() );
1066                if ( configLayer != null ) {
1067                    layers.put( layer.getName(), configLayer );
1068                } else {
1069                    throw new PrintMapServiceException( Messages.getMessage( "WMPS_UNKNOWN_LAYER", layer.getName() ) );
1070                }
1071            }
1072    
1073            return layers;
1074        }
1075    
1076        /**
1077         * Create a bounding box if no bounding box has been passed along with the PrintMap request.
1078         * 
1079         * @param printMap
1080         * @param center
1081         * @param scaleDenominator
1082         * @param mapWidth
1083         * @param mapHeight
1084         * @return Envelope
1085         */
1086        private Envelope createBBOX( PrintMap printMap, Point center, int scaleDenominator, double mapWidth,
1087                                     double mapHeight ) {
1088            int dpi = printMap.getDpi();
1089            if ( dpi < 0 ) {
1090                dpi = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
1091            }
1092            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
1093                LOG.logDebug( "calculating BBOX using folloing parameter:" );
1094                LOG.logDebug( "map center: ", center );
1095                LOG.logDebug( "target scale: ", scaleDenominator );
1096                LOG.logDebug( "target resolution (dpi): ", dpi );
1097                LOG.logDebug( "map width: ", mapWidth );
1098                LOG.logDebug( "map height: ", mapHeight );
1099            }
1100            // screen -> world projection
1101            double pixelSize = WMPSConfiguration.INCH2M / dpi;
1102            double w2 = ( scaleDenominator * pixelSize * mapWidth ) / 2d;
1103            double x1 = center.getX() - w2;
1104            double x2 = center.getX() + w2;
1105            w2 = ( scaleDenominator * pixelSize * mapHeight ) / 2d;
1106            double y1 = center.getY() - w2;
1107            double y2 = center.getY() + w2;
1108    
1109            Envelope bbox = GeometryFactory.createEnvelope( x1, y1, x2, y2, center.getCoordinateSystem() );
1110    
1111            LOG.logDebug( "calculated BBOX: ", bbox );
1112            return bbox;
1113    
1114        }
1115    
1116        /**
1117         * Returns the current configuration used to initialise the PrintMapHandler.
1118         * 
1119         * @return WMPSConfiguration
1120         */
1121        public WMPSConfiguration getConfiguration() {
1122            return this.configuration;
1123        }
1124    
1125        /**
1126         * Parse the Template and retrieve the page width, page height information.
1127         * 
1128         * @param printMap
1129         * @return int[]
1130         * @throws PrintMapServiceException
1131         * @throws IOException
1132         */
1133        private int[] getMapParamsFromTemplate( PrintMap printMap )
1134                                throws PrintMapServiceException, IOException {
1135            String templateName = printMap.getTemplate();
1136            int[] mapParams = null;
1137            URL file = null;
1138            // try {
1139            boolean isCompiled = false;
1140            file = getTemplatePath( templateName, isCompiled );
1141            if ( file != null ) {
1142                Document dom = null;
1143                try {
1144                    InputStream is = file.openStream();
1145                    int c = 0;
1146                    StringBuffer sb = new StringBuffer( 10000 );
1147                    while ( ( c = is.read() ) > -1 ) {
1148                        sb.append( (char) c );
1149                    }
1150                    is.close();
1151                    // hack to ensure reporting engine is working even if the
1152                    // jasper-report server is not available
1153                    String s = StringTools.replace(
1154                                                    sb.toString(),
1155                                                    "<!DOCTYPE jasperReport PUBLIC \"//JasperReports//DTD Report Design//EN\" \"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd\">",
1156                                                    "", false );
1157    
1158                    dom = XMLTools.parse( new StringReader( s ) );
1159                    // XMLFragment xml = new XMLFragment( file );
1160                    mapParams = parseImageNodes( printMap, dom.getDocumentElement() );
1161                } catch ( Exception e ) {
1162                    LOG.logError( e.getMessage(), e );
1163                    throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_PARSING_TEMPLATE", file ) );
1164                }
1165            }
1166    
1167            return mapParams;
1168        }
1169    
1170        /**
1171         * Return the url for the current (jasper reports) template.
1172         * 
1173         * @param templateName
1174         * @param isCompiled
1175         * @return URL
1176         * @throws PrintMapServiceException
1177         */
1178        private URL getTemplatePath( String templateName, boolean isCompiled )
1179                                throws IOException {
1180    
1181            if ( isCompiled ) {
1182                templateName = templateName + ".jasper";
1183            } else {
1184                templateName = templateName + ".jrxml";
1185            }
1186    
1187            PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam();
1188            URL fileURL = new URL( printMapParam.getTemplateDirectory() + templateName );
1189    
1190            LOG.logDebug( "Retrieved the template file url. " + fileURL );
1191    
1192            return fileURL;
1193        }
1194    
1195        /**
1196         * Gets the Image node defined to hold the 'Map' and the node defined to hold the 'Legend'.
1197         * 
1198         * @param root
1199         * @return List
1200         * @throws PrintMapServiceException
1201         */
1202        private int[] parseImageNodes( PrintMap printMap, Element root )
1203                                throws PrintMapServiceException {
1204    
1205            int[] mapParams = new int[4];
1206            int mapCount = 0;
1207            try {
1208                NamespaceContext nsc = CommonNamespaces.getNamespaceContext();
1209                nsc.addNamespace( "jasper", URI.create( "http://jasperreports.sourceforge.net/jasperreports" ) );
1210                List<Node> images = XMLTools.getNodes( root, "detail/band/image", null );
1211                if ( images == null || images.size() == 0 ) {
1212                    images = XMLTools.getNodes( root, "jasper:detail/jasper:band/jasper:image", nsc );
1213                }
1214                for ( int i = 0; i < images.size(); i++ ) {
1215                    Node image = images.get( i );
1216                    // e.g. $P{MAP}
1217                    String value = XMLTools.getNodeAsString( image, "imageExpression", null, null );
1218                    if ( value == null ) {
1219                        value = XMLTools.getRequiredNodeAsString( image, "jasper:imageExpression", nsc );
1220                    }
1221                    int idx = value.indexOf( "{" );
1222                    if ( idx != -1 ) {
1223    
1224                        String tmp = value.substring( idx + 1, value.length() - 1 );
1225                        Element reportElement = (Element) XMLTools.getNode( image, "reportElement", null );
1226                        if ( reportElement == null ) {
1227                            reportElement = (Element) XMLTools.getRequiredNode( image, "jasper:reportElement", nsc );
1228                        }
1229                        String width = reportElement.getAttribute( "width" );
1230                        String height = reportElement.getAttribute( "height" );
1231    
1232                        double dpi = printMap.getDpi();
1233                        if ( dpi < 0 ) {
1234                            dpi = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
1235                        }
1236                        // Templates created by iReport assumes a resolution of 72 dpi
1237                        if ( tmp.startsWith( "MAP" ) ) {
1238                            mapParams[0] = (int) ( Integer.parseInt( width ) / 72d * dpi );
1239                            mapParams[1] = (int) ( Integer.parseInt( height ) / 72d * dpi );
1240                            mapCount = mapCount + 1;
1241                        } else if ( tmp.startsWith( "LEGEND" ) ) {
1242                            mapParams[2] = (int) ( Integer.parseInt( width ) / 72d * dpi );
1243                            mapParams[3] = (int) ( Integer.parseInt( height ) / 72d * dpi );
1244                        }
1245                    }
1246                }
1247                if ( ( mapCount == 0 ) || ( mapCount > 1 ) ) {
1248                    throw new PrintMapServiceException( Messages.getMessage( "WMPS_TOO_MANY_MAPAREAS", mapCount ) );
1249                }
1250            } catch ( XMLParsingException e ) {
1251                LOG.logError( e.getMessage(), e );
1252                throw new PrintMapServiceException( Messages.getMessage( "WMPS_INVALID_JASPER_TEMPLATE" ) );
1253            }
1254    
1255            return mapParams;
1256        }
1257    
1258    }