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 }