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