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