//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/ogcwebservices/wmps/PrintMapHandler.java,v 1.79 2006/11/27 09:07:52 poth Exp $ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstraße 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.ogcwebservices.wmps; import java.awt.Color; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.Image; import java.awt.geom.AffineTransform; import java.awt.image.AffineTransformOp; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; import java.sql.Connection; import java.sql.SQLException; import java.text.MessageFormat; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import net.sf.jasperreports.engine.JREmptyDataSource; import net.sf.jasperreports.engine.JRException; import net.sf.jasperreports.engine.JasperExportManager; import net.sf.jasperreports.engine.JasperFillManager; import net.sf.jasperreports.engine.JasperPrint; import org.deegree.datatypes.values.Values; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.ImageUtils; import org.deegree.framework.util.MapUtils; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.XMLFragment; import org.deegree.framework.xml.XMLParsingException; import org.deegree.framework.xml.XMLTools; import org.deegree.graphics.sld.StyledLayerDescriptor; import org.deegree.graphics.transformation.WorldToScreenTransform; import org.deegree.i18n.Messages; import org.deegree.model.crs.CRSFactory; import org.deegree.model.crs.CoordinateSystem; import org.deegree.model.crs.UnknownCRSException; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.GeometryFactory; import org.deegree.model.spatialschema.Point; import org.deegree.ogcwebservices.InconsistentRequestException; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.wmps.configuration.PrintMapParam; import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration; import org.deegree.ogcwebservices.wmps.operation.PrintMap; import org.deegree.ogcwebservices.wmps.operation.PrintMapResponseDocument; import org.deegree.ogcwebservices.wmps.operation.TextArea; import org.deegree.ogcwebservices.wms.capabilities.Layer; import org.deegree.ogcwebservices.wms.capabilities.LegendURL; import org.deegree.ogcwebservices.wms.capabilities.ScaleHint; import org.deegree.ogcwebservices.wms.capabilities.Style; import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource; import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource; import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource; import org.deegree.ogcwebservices.wms.operation.GetMap; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; /** * Handles the PrintMap request. Retrieves the request from the DB and creates a pdf file. * * @author <a href="mailto:deshmukh@lat-lon.de">Anup Deshmukh</a> * @author last edited by: $Author: poth $ * * @version 2.0, $Revision: 1.79 $, $Date: 2006/11/27 09:07:52 $ */ public class PrintMapHandler implements Runnable { private static final ILogger LOG = LoggerFactory.getLogger( PrintMapHandler.class ); private final double TILE_MAX_SIZE = 800; private final String FORMAT = ".png"; private final String MIMETYPE = "image/png"; private final String EXCEPTION = "application/vnd.ogc.se_inimage"; private WMPSConfiguration configuration; private String message; /** * Creates a new instance of the PrintMapHandler for the current configuration. * * @param configuration */ public PrintMapHandler( WMPSConfiguration configuration ) { this.configuration = configuration; } /** * Run a new thread for each PrintMap request. This Thread runs till no more PrintMap requests * are available in the DB. * * @see java.lang.Runnable#run() */ public void run() { RequestManager manager = null; PrintMapResponseDocument response = null; WMPSDatabase wmpsDB = null; Connection connection = null; try { wmpsDB = new WMPSDatabase(this.configuration.getDeegreeParams().getCacheDatabase() ); connection = wmpsDB.acquireConnection(); while ( true ) { // request aus db holen PrintMap printMap = wmpsDB.selectPrintMapRequest( connection ); if ( printMap == null ) { // abort(); wmpsDB.releaseConnection( connection ); break; } // Kartenbild erzeugen LOG.logDebug( "Performing print map" ); manager = new DefaultRequestManager( this.configuration, printMap ); performPrintMap( printMap, false ); wmpsDB.updateDB( connection, printMap.getId(), printMap.getTimestamp() ); response = manager.createFinalResponse( this.message, null ); manager.sendEmail( response ); wmpsDB.releaseConnection( connection ); LOG.logDebug( "Done performing PrintMap request." ); } } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); if ( manager != null ) { try { response = manager.createFinalResponse( this.message, e.getMessage() ); manager.sendEmail( response ); } catch ( Exception e1 ) { // should just happen if mail server is not reachable // in this case the error just can be logged XMLFragment doc = new XMLFragment( response.getRootElement() ); LOG.logDebug( doc.getAsString() ); LOG.logError( e.getMessage(), e ); } } } finally { try { if ( !connection.isClosed() ) { wmpsDB.releaseConnection( connection ); } } catch ( SQLException e ) { // should never happen LOG.logError( e.getMessage(), e ); } } } /** * performs a sychronous printMap processing * * @param printMap * @return byte[] * @throws Exception */ public byte[] runSynchronous( PrintMap printMap ) throws Exception { return performPrintMap( printMap, true ); } /** * From each PrintMap request run the WMS GetMap request. * * @param printMap * @param synchronous * @return byte[] * @throws PrintMapServiceException * @throws IOException */ private byte[] performPrintMap( PrintMap printMap, boolean synchronous ) throws PrintMapServiceException, IOException { Map config_layers = retrieveLayersFromConfig( printMap ); int[] mapParams = getMapParamsFromTemplate( printMap.getTemplate() ); int scaleDenominator = printMap.getScaleDenominator(); Envelope bbox = printMap.getBBOX(); if ( bbox == null ) { LOG.logDebug( "BBOX not defined. Using the center and scale to calculate a new BBOX." ); Point center = printMap.getCenter(); bbox = createBBOX( center, scaleDenominator, mapParams[0], mapParams[1] ); } else { //Adjust aspect ratio of bbox to aspect ratio of template map area double bboxAspectRatio = bbox.getWidth() / bbox.getHeight(); double printAspectRatio = ((double) mapParams[0]) / mapParams[1]; if (bboxAspectRatio > printAspectRatio) { double centerY = bbox.getMin().getY() + ((bbox.getMax().getY() - bbox.getMin().getY()) / 2d); double height = bbox.getWidth() * ( 1.0 / printAspectRatio ); double minY = centerY - ( height / 2d); double maxY = centerY + ( height / 2d); bbox = GeometryFactory.createEnvelope( bbox.getMin().getX(), minY, bbox.getMax().getX(), maxY, bbox.getCoordinateSystem() ); } else { double centerX = bbox.getMin().getX() + ((bbox.getMax().getX() - bbox.getMin().getX()) / 2d); double width = bbox.getHeight() * printAspectRatio ; double minX = centerX - ( width / 2d ); double maxX = centerX + ( width / 2d ); bbox = GeometryFactory.createEnvelope( minX, bbox.getMin().getY(), maxX, bbox.getMax().getY(), bbox.getCoordinateSystem() ); } } if ( scaleDenominator == -1 ) { LOG.logDebug( "Scale not defined. Using MapUtil to calculate the scale denominator for the current bbox and map sizes" ); double pixelSize = 0.0254 / configuration.getDeegreeParams().getPrintMapParam().getTargetResolution(); scaleDenominator = (int) MapUtils.calcScale( mapParams[0], mapParams[1], bbox, bbox.getCoordinateSystem(), pixelSize ); } BufferedImage mapImage = null; try { mapImage = performBuildMapImage( config_layers, printMap, bbox, mapParams[0], mapParams[1] ); saveImageToDisk( printMap, mapImage, "MAP" ); String s = StringTools.concat( 100, "Retrieved PrintMap request '", printMap.getId(), "' and saved to disk" ); LOG.logDebug( s ); } catch ( OGCWebServiceException e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_PERFORMING_PRINTMAP" ) ); } catch ( IOException e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_WRITING_MAP_TMP_FILE" ) ); } if ( printMap.getLegend() ) { try { BufferedImage legendImage = performGetLegend( config_layers, printMap, mapParams ); saveImageToDisk( printMap, legendImage, "LEGEND" ); LOG.logDebug( "Saved the legend image file to disk." ); } catch ( IOException e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_WRITING_LEGEND_TMP_FILE" ) ); } } byte[] b = exportOutput( printMap, scaleDenominator, synchronous ); return b; } /** * Build the Map image for the current PrintMap request. Vector and Raster layers are handled * seperately. * * @param config_layers * @param printMap * @param bbox * @param width * @param height * @return BufferedImage * @throws OGCWebServiceException */ private BufferedImage performBuildMapImage( Map config_layers, PrintMap printMap, Envelope bbox, double width, double height ) throws OGCWebServiceException { BufferedImage targetImage = new BufferedImage( (int) width, (int) height, BufferedImage.TYPE_INT_ARGB ); Graphics2D g = (Graphics2D) targetImage.getGraphics(); if ( !printMap.getTransparent() ) { g.setBackground( printMap.getBGColor() ); } handleLayerDatasources( config_layers, printMap, bbox, width, height, g ); g.dispose(); return targetImage; } /** * Determines the datasource for each layer(vector, raster). * * @param config_layers * @param printMap * @param bbox * @param width * @param height * @param g * @throws OGCWebServiceException * @throws InconsistentRequestException */ private void handleLayerDatasources( Map config_layers, PrintMap printMap, Envelope bbox, double width, double height, Graphics g ) throws OGCWebServiceException, InconsistentRequestException { double px = 0.0254 / configuration.getDeegreeParams().getPrintMapParam().getTargetResolution(); CoordinateSystem crs; try { crs = CRSFactory.create( printMap.getSRS() ); } catch ( UnknownCRSException e1 ) { throw new InconsistentRequestException( e1.getMessage() ); } double scale = MapUtils.calcScale( (int)width, (int)height, bbox, crs, px ); GetMap.Layer[] printMapLayers = printMap.getLayers(); for ( int i = 0; i < printMapLayers.length; i++ ) { String name = printMapLayers[i].getName(); Layer configLayer = (Layer) config_layers.get( name ); ScaleHint scaleHint = configLayer.getScaleHint(); if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax() ) { String type = determineDatasourceType( configLayer, scale ); if ( type != null && "vector".equalsIgnoreCase( type ) ) { GetMap.Layer[] lay = null; if ( ( printMapLayers.length - i ) > 1 ) { lay = getContinuousVectorLayer( config_layers, printMapLayers, scale, i ); } else { lay = new GetMap.Layer[] { printMapLayers[i] }; } try { handleVectorDataLayer( printMap, bbox, width, height, g, lay ); } catch ( OGCWebServiceException e ) { LOG.logError( e.getMessage(), e ); throw new OGCWebServiceException( Messages.getMessage( "WMPS_ERROR_HANDLING_GETMAP", name ) ); } // Skip the number of layers already handled. if ( lay.length != 1 ) { i = i + ( lay.length - 1 ); } } else { // must be a raster data layer GetMap.Layer[] lay = new GetMap.Layer[] { printMapLayers[i] }; try { handleRasterDataLayer( printMap, bbox, width, height, g, lay ); } catch ( OGCWebServiceException e ) { LOG.logError( e.getMessage(), e ); throw new OGCWebServiceException( Messages.getMessage( "WMPS_ERROR_HANDLING_GETMAP", name ) ); } } } else { String s = StringTools.concat( 100, "No Datasource available for layer: ", name, " at scale: ", scale ); LOG.logInfo( s ); } } } /** * returns an array of layers that: * <ul> * <li>a) made of vector data * <li>b) are continous in the requested list of layers * </ul> * * @param config_layers * @param printMapLayers * @param mapScale scale of the entire map * @param i * @return Layer[] */ private GetMap.Layer[] getContinuousVectorLayer( Map config_layers, GetMap.Layer[] printMapLayers, double mapScale, int i ) { List<GetMap.Layer> layers = new ArrayList<GetMap.Layer>( printMapLayers.length ); int counter = 0; for ( ; i < printMapLayers.length; i++ ) { String name = printMapLayers[i].getName(); Layer configLayer = (Layer) config_layers.get( name ); String type = determineDatasourceType( configLayer, mapScale ); if ( "vector".equals( type ) ) { layers.add( counter, printMapLayers[i] ); counter++; } else { break; } } return layers.toArray( new GetMap.Layer[layers.size()] ); } /** * Perform the GetMap request for vector layers. * * @param printMap * @param bbox * @param width * @param height * @param g * @param lay * @throws OGCWebServiceException */ private void handleVectorDataLayer( PrintMap printMap, Envelope bbox, double width, double height, Graphics g, GetMap.Layer[] lay ) throws OGCWebServiceException { URL sldURL = null; StyledLayerDescriptor sld = null; GetMap getMap = GetMap.create( printMap.getVersion(), printMap.getId(), lay, null, null, this.MIMETYPE, (int) width, (int) height, printMap.getSRS(), bbox, printMap.getTransparent(), printMap.getBGColor(), this.EXCEPTION, null, sldURL, sld, printMap.getVendorSpecificParameters() ); DefaultGetMapHandler gmh = new DefaultGetMapHandler( this.configuration, getMap ); gmh.performGetMap( g ); } /** * Perform the GetMap request for each raster layer. Here the raster layer is divided into tiles * for memory handling efficiency. * * @param printMap * @param bbox * @param width * @param height * @param g * @param lay * @throws OGCWebServiceException */ private void handleRasterDataLayer( PrintMap printMap, Envelope bbox, double width, double height, Graphics g, GetMap.Layer[] lay ) throws OGCWebServiceException { // Get Map (missing) parameters. Values elevation = null; Map<String, Values> sampleDimension = null; Values time = null; URL sldURL = null; StyledLayerDescriptor sld = null; boolean xRemainder = false; int wtx = (int) width % (int) this.TILE_MAX_SIZE; int nkx = (int) width / (int) this.TILE_MAX_SIZE; if ( wtx > 0 ) { xRemainder = true; nkx++; } boolean yRemainder = false; int wty = (int) height % (int) this.TILE_MAX_SIZE; int nky = (int) height / (int) this.TILE_MAX_SIZE; if ( wty > 0 ) { yRemainder = true; nky++; } WorldToScreenTransform trans = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(), bbox.getMax().getX(), bbox.getMax().getY(), 0d, 0d, width, height ); for ( int x = 0; x < nkx; x++ ) { double tileWidth = this.TILE_MAX_SIZE; if ( xRemainder ) { if ( x == nkx - 1 ) { tileWidth = wtx; } } for ( int y = 0; y < nky; y++ ) { double tileHeight = this.TILE_MAX_SIZE; if ( yRemainder ) { if ( y == nky - 1 ) { tileHeight = wty; } } BufferedImage bi = new BufferedImage( (int) tileWidth, (int) tileHeight, BufferedImage.TYPE_INT_ARGB ); Graphics tileg = bi.getGraphics(); // calc bbox Envelope bb = calculateTileBBOX( trans, x, y, tileWidth, tileHeight, bbox.getCoordinateSystem() ); // create GetMap GetMap getMap = GetMap.create( printMap.getVersion(), printMap.getId(), lay, elevation, sampleDimension, this.MIMETYPE, (int) tileWidth, (int) tileHeight, printMap.getSRS(), bb, printMap.getTransparent(), printMap.getBGColor(), this.EXCEPTION, time, sldURL, sld, printMap.getVendorSpecificParameters() ); // performGetMap( tileg ); DefaultGetMapHandler gmh = new DefaultGetMapHandler( this.configuration, getMap ); gmh.performGetMap( tileg ); tileg.dispose(); g.drawImage( bi, (int) ( x * this.TILE_MAX_SIZE ), (int) ( y * this.TILE_MAX_SIZE ), null ); } } } /** * Calculate the tile BBOX for the raster datalayer. * * @param trans * @param x * @param y * @param tileWidth * @param tileHeight * @param crs * @return Envelope */ private Envelope calculateTileBBOX( WorldToScreenTransform trans, int x, int y, double tileWidth, double tileHeight, CoordinateSystem crs ) { double x1 = x * this.TILE_MAX_SIZE; double y1 = y * this.TILE_MAX_SIZE; double x2 = x1 + tileWidth; double y2 = y1 + tileHeight; double minX = trans.getSourceX( x1 ); double maxX = trans.getSourceX( x2 ); double minY = trans.getSourceY( y2 ); double maxY = trans.getSourceY( y1 ); return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, crs ); } /** * Parses the Layer datastores to determine the type of the layer. * Layers having a vector datasource as well as a raster datasource * for the passed mapScale will be treated as raster layers * * @param layer * @param mapScale scale of the entire map * @return String either raster, vector or nodatasource */ private String determineDatasourceType( Layer layer, double mapScale ) { AbstractDataSource[] ads = layer.getDataSource(); String type = null; boolean[] mixed = new boolean[] { false, false }; for ( int i = 0; i < ads.length; i++ ) { ScaleHint scaleHint = ads[i].getScaleHint(); if ( mapScale >= scaleHint.getMin() && mapScale < scaleHint.getMax() ) { if ( ( ads[i] instanceof RemoteWMSDataSource ) || ( ads[i] instanceof RemoteWCSDataSource ) || ( ads[i] instanceof LocalWCSDataSource ) ) { type = "raster"; mixed[0] = true; } else { type = "vector"; mixed[1] = true; } } } if ( mixed[0] && mixed[1] ) { // Layers having a vector datasource as well as a raster datasource // for the passed mapScale will be treated as raster layers type = "raster"; } return type; } /** * Retrieve the legend images from the URL given in the configuration layers. * * * @param layerDefs * @param printMap * @param mapParams * @return BufferedImage * @throws OGCWebServiceException * @throws InconsistentRequestException */ private BufferedImage performGetLegend( Map layerDefs, PrintMap printMap, int[] mapParams ) { GetMap.Layer[] layers = printMap.getLayers(); Map<String,Image> legendImg = new HashMap<String,Image>(layers.length); int height = 0; int maxWidth = 0; for ( int i = 0; i < layers.length; i++ ) { String name = layers[i].getName(); String styleName =layers[i].getStyleName(); Layer configLayer = (Layer) layerDefs.get( name ); Style style = configLayer.getStyleResource( styleName ); LegendURL[] lu = style.getLegendURL(); if ( lu != null && lu.length > 0 ) { int k = 0; boolean drawn = false; while ( k < lu.length && !drawn ) { URL url = lu[k++].getOnlineResource(); try { Image img = ImageUtils.loadImage( url ); legendImg.put( name, img ); drawn = true; } catch ( IOException e ) { // we do not throw the exception bacause there are maybe // further URLs we can try and even if not the user will // be informed by a special legend symbol that no correct // symbol can be accessed LOG.logError( "can not access LegendURL: " + url, e ); } } if ( !drawn ) { // if legend URL(s) are defined but none of them can // be accessed String s = StringTools.concat( 100, "no legend URL accessable for layer: ", name, "; style: ", styleName, " using dummy legend image"); LOG.logError( s ); BufferedImage img = drawMissingLegendURLImage( s ); legendImg.put( name, img ); } } else { // if no legend URL has been defined which probably is the case // for WMS no supporting GetLegendGraphic operation String s = StringTools.concat( 100, "no legend URL available for layer: ", name, "; style: ", styleName, " using dummy legend image"); LOG.logError( s ); BufferedImage img = drawMissingLegendURLImage( s ); legendImg.put( name, img ); } // update all over legend height and width BufferedImage img = (BufferedImage)legendImg.get( name ); if ( img.getWidth() > maxWidth ) { maxWidth = img.getWidth(); } height += img.getHeight(); } // depending on the size of the legend all legend symbols must scaled by // the same factor to fit the legend size defined in the current template double dh = calcDeltaLegend( mapParams[2], mapParams[3], height, maxWidth ); // create an empty basic image as target for painting all legend symbols BufferedImage actualLegendImage = new BufferedImage( mapParams[2], mapParams[3], BufferedImage.TYPE_INT_ARGB ); Graphics2D g = (Graphics2D) actualLegendImage.getGraphics(); int y = 0; for ( int i = layers.length; i > 0 ; i-- ) { // draw all legend symbols in correct order String name = layers[i-1].getName(); BufferedImage img = scaleImage( (BufferedImage)legendImg.get( name ), dh ); g.drawImage( img, 0, y, null ); y += img.getHeight(); } g.dispose(); return actualLegendImage; } private BufferedImage drawMissingLegendURLImage( String text ) { BufferedImage img = new BufferedImage( 550, 50, BufferedImage.TYPE_INT_ARGB ); Graphics g = img.getGraphics(); g.setColor( Color.YELLOW ); g.fillRect( 0, 0, img.getWidth(), img.getHeight() ); g.setColor( Color.RED ); g.drawString( text, 10, 20 ); g.dispose(); return img; } /** * calculates factor for resizing legend images * * @param legendWidth * The width of the legend area * @param legendHeight * The height of the legend area * @param height * The height of all legends put together * @param maxWidth * The width of the wides legend * @return Returns the factor for resizing legend images */ private double calcDeltaLegend( int legendWidth, int legendHeight, int height, int maxWidth ) { double dh = legendHeight / (double)height; double dw = legendWidth / (double)maxWidth; if ( dw < dh ) { return dw; } return dh; } /** * Scale Image. * * @param image * @param ratio * @return BufferedImage */ private BufferedImage scaleImage( BufferedImage image, double ratio ) { AffineTransform tx = new AffineTransform(); tx.scale( ratio, ratio ); AffineTransformOp op = new AffineTransformOp( tx, AffineTransformOp.TYPE_BILINEAR ); return op.filter( image, null ); } /** * Save the GetMap image to the disk. * * @param printMap * @param image * @param type * @throws IOException */ private void saveImageToDisk( PrintMap printMap, BufferedImage image, String type ) throws IOException { String fileName = null; String templateName = printMap.getTemplate(); if ( type.equalsIgnoreCase( "MAP" ) ) { fileName = StringTools.concat( 200, "Map_", templateName, '_', printMap.getId(), this.FORMAT ); } else if ( type.equalsIgnoreCase( "LEGEND" ) ) { fileName = StringTools.concat( 200, "Legend_", templateName, '_', printMap.getId(), this.FORMAT ); } PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam(); String path = printMapParam.getPlotImageDir() + '/' + fileName; URL downloadDirectory = new URL( path ); try { ImageUtils.saveImage( image, new File( downloadDirectory.toURI() ), 1 ); } catch ( URISyntaxException e ) { // should never happen because each valid URL is a valid URI too LOG.logError( e.getMessage(), e ); } } /** * Use JasperReports to create a pdf file. The Jasper Template will be loaded and a dynamic link * will be created to the image on the disk. * * @param printMap * @param scaleDenominator * @param synchronous * @return byte[] * @throws PrintMapServiceException */ private byte[] exportOutput( PrintMap printMap, int scaleDenominator, boolean synchronous ) throws PrintMapServiceException { // generate a file using JasperReports. byte[] b = null; try { JasperPrint print = fillJasperTemplate( printMap, scaleDenominator ); b = doJasperPrintExport( printMap, print, synchronous ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_SAVING_PDF" ) ); } return b; } /** * Open the JasperAPI to process the PrintMap request. * * @param printMap * @param scaleDenominator * @return JasperPrint * @throws InconsistentRequestException * @throws JRException * @throws MalformedURLException */ private JasperPrint fillJasperTemplate( PrintMap printMap, int scaleDenominator ) throws JRException, MalformedURLException { URL templatePath = null; try { String templateName = printMap.getTemplate(); templatePath = getTemplatePath( templateName, true ); } catch ( IOException e ) { LOG.logError( e.getMessage(), e ); throw new MalformedURLException( Messages.getMessage( "WMPS_ERROR_CREATING_TEMPLATEPATH", printMap.getTemplate() ) ); } Map<String, Object> parameters = new HashMap<String, Object>(); URL mapImagePath = getResultImagePath( printMap, "Map" ); parameters.put( "MAP", mapImagePath.getFile() ); if ( printMap.getLegend() ) { URL legendImagePath = getResultImagePath( printMap, "Legend" ); parameters.put( "LEGEND", legendImagePath.getFile() ); } String scale = "1:" + scaleDenominator; if ( printMap.getScaleBar() == true ) { parameters.put( "SCALE", scale ); } TextArea[] textAreas = printMap.getTextAreas(); if ( textAreas != null && textAreas.length >0 ) { for ( int i = 0; i < textAreas.length; i++ ) { TextArea textArea = textAreas[i]; LOG.logDebug( "Names and text fields entered, extracted." ); String name = textArea.getName(); String text = textArea.getText(); if ( name != null ) { LOG.logDebug( "If name is not null, allocate it to the hashmap 'parameters' in uppercase." ); parameters.put( name.toUpperCase(), text ); } } } else { String title = printMap.getTitle(); if ( title != null ) { parameters.put( "TITLE", title ); } String copyright = printMap.getCopyright(); if ( copyright != null ) { parameters.put( "COPYRIGHT", copyright ); } String note = printMap.getNote(); if ( note != null ) { parameters.put( "NOTE", note ); } } JasperPrint print = null; try { print = JasperFillManager.fillReport( templatePath.getFile(), parameters, new JREmptyDataSource() ); } catch ( JRException e ) { LOG.logError( e.getMessage(), e ); throw new JRException( Messages.getMessage( "WMPS_ERROR_BUILDING_TEMPLATE", templatePath ) ); } return print; } /** * Retrieve the result map image file path for the current request. * * @param printMap * @param type * @return URL * @throws MalformedURLException */ private URL getResultImagePath( PrintMap printMap, String type ) throws MalformedURLException { String templateName = printMap.getTemplate(); String fileName = type + "_" + templateName + "_" + printMap.getId() + this.FORMAT; PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam(); String path = printMapParam.getPlotImageDir() + '/' + fileName; URL imagePath = new URL( path ); return imagePath; } /** * Print the layer to a the specified format. * * @param printMap * @param print * @param synchronous * @return byte[] * @throws JRException * @throws IOException */ private byte[] doJasperPrintExport( PrintMap printMap, JasperPrint print, boolean synchronous ) throws JRException, IOException { String format = this.configuration.getDeegreeParams().getPrintMapParam().getFormat(); String templateName = printMap.getTemplate(); String filename = StringTools.concat( 200, format, '_', templateName, '_', printMap.getId(), '.', format ); String directory = this.configuration.getDeegreeParams().getPrintMapParam().getPlotDirectory(); URL downloadFile = new URL( directory + '/' + filename ); byte[] b = null; try { if ( synchronous ) { b = doSynchronousProcessing( print, format ); } else { doSaveResultDocument( print, format, downloadFile ); createMailLink( filename ); } } catch ( JRException e ) { LOG.logError( e.getMessage(), e ); throw new JRException( Messages.getMessage( "WMPS_ERROR_PRINTING_REPORT", format, downloadFile.getFile() ) ); } return b; } /** * Create a mail link to be sent to the user email address. The mail link allows the user to * open the pdf document for viewing and downloading purposes. Here 2 cases are taken into * consideration * <ul> * <li> An authentification servlet link will be sent to the client. * <li> A direct access to the clients data file. * </ul> * * @param printMap * @param filename */ private void createMailLink( String filename ) { PrintMapParam pmp = this.configuration.getDeegreeParams().getPrintMapParam(); String onlineResource = pmp.getOnlineResource(); String template = pmp.getMailTextTemplate(); this.message = MessageFormat.format( template, new Object[] { onlineResource, filename.trim() } ); } /** * Save the result document using the JasperExportManager to the file specified. * * @param print * @param format * @param downloadDirectory * @throws JRException */ private void doSaveResultDocument( JasperPrint print, String format, URL downloadDirectory ) throws JRException { if ( format.equalsIgnoreCase( "pdf" ) ) { LOG.logDebug( "Exporting as pdf to file " + downloadDirectory.getFile() ); JasperExportManager.exportReportToPdfFile( print, downloadDirectory.getFile() ); } else if ( format.equalsIgnoreCase( "html" ) ) { LOG.logDebug( "Exporting as html to file " + downloadDirectory.getFile() ); JasperExportManager.exportReportToHtmlFile( print, downloadDirectory.getFile() ); } else if ( format.equalsIgnoreCase( "xml" ) ) { LOG.logDebug( "Exporting as xml to file " + downloadDirectory.getFile() ); JasperExportManager.exportReportToXmlFile( print, downloadDirectory.getFile(), false ); } } /** * Export the result document to a stream to be returend to the waiting client. * * @param print * @param format * @return byte[] * @throws JRException */ private byte[] doSynchronousProcessing( JasperPrint print, String format ) throws JRException { byte[] b; ByteArrayOutputStream bos = new ByteArrayOutputStream( 100000 ); if ( format.equalsIgnoreCase( "pdf" ) ) { JasperExportManager.exportReportToPdfStream( print, bos ); } else if ( format.equalsIgnoreCase( "xml" ) ) { JasperExportManager.exportReportToXmlStream( print, bos ); } b = bos.toByteArray(); return b; } /** * Retrieve PrintMap request layers from the WMPSConfiguration file. Counter check if the layer * has been defined and also get the type of datasource used by the layer. * * @param printMap * @return Map key-> layer name, value -> configLayer * @throws PrintMapServiceException */ private Map retrieveLayersFromConfig( PrintMap printMap ) throws PrintMapServiceException { GetMap.Layer[] requestedLayers = printMap.getLayers(); Map<String, Layer> layers = new HashMap<String, Layer>(); for ( int i = 0; i < requestedLayers.length; i++ ) { GetMap.Layer layer = requestedLayers[i]; Layer configLayer = this.configuration.getLayer( layer.getName() ); if ( configLayer != null ) { layers.put( layer.getName(), configLayer ); } else { throw new PrintMapServiceException( Messages.getMessage( "WMPS_UNKNOWN_LAYER", layer.getName() ) ); } } return layers; } /** * Create a bounding box if no bounding box has been passed along with the PrintMap request. * * @param center * @param scaleDenominator * @param mapWidth * @param mapHeight * @return Envelope */ private Envelope createBBOX( Point center, int scaleDenominator, double mapWidth, double mapHeight ) { // screen -> world projection double pixelSize = 0.0254 / configuration.getDeegreeParams().getPrintMapParam().getTargetResolution(); double w2 = ( scaleDenominator * pixelSize * mapWidth ) / 2d; double x1 = center.getX() - w2; double x2 = center.getX() + w2; w2 = ( scaleDenominator * pixelSize * mapHeight ) / 2d; double y1 = center.getY() - w2; double y2 = center.getY() + w2; Envelope bbox = GeometryFactory.createEnvelope( x1, y1, x2, y2, center.getCoordinateSystem() ); return bbox; } /** * Returns the current configuration used to initialise the PrintMapHandler. * * @return WMPSConfiguration */ public WMPSConfiguration getConfiguration() { return this.configuration; } /** * Parse the Template and retrieve the page width, page height information. * * @param templateName * @return int[] * @throws PrintMapServiceException * @throws IOException */ private int[] getMapParamsFromTemplate( String templateName ) throws PrintMapServiceException, IOException { int[] mapParams = null; URL file = null; // try { boolean isCompiled = false; file = getTemplatePath( templateName, isCompiled ); if ( file != null ) { Document dom = null; try { InputStream is = file.openStream(); int c = 0; StringBuffer sb = new StringBuffer( 10000 ); while ( ( c = is.read() ) > -1 ) { sb.append( (char) c ); } is.close(); // hack to ensure reporting engine is working even if the // jasper-report server is not available String s = StringTools.replace( sb.toString(), "<!DOCTYPE jasperReport PUBLIC \"//JasperReports//DTD Report Design//EN\" \"http://jasperreports.sourceforge.net/dtds/jasperreport.dtd\">", "", false ); dom = XMLTools.parse( new StringReader( s ) ); // XMLFragment xml = new XMLFragment( file ); mapParams = parseImageNodes( dom.getDocumentElement() ); } catch ( Exception e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_ERROR_PARSING_TEMPLATE", file ) ); } } return mapParams; } /** * Return the url for the current (jasper reports) template. * * @param templateName * @param isCompiled * @return URL * @throws PrintMapServiceException */ private URL getTemplatePath( String templateName, boolean isCompiled ) throws IOException { if ( isCompiled ) { templateName = templateName + ".jasper"; } else { templateName = templateName + ".jrxml"; } PrintMapParam printMapParam = this.configuration.getDeegreeParams().getPrintMapParam(); URL fileURL = new URL( printMapParam.getTemplateDirectory() + templateName ); LOG.logDebug( "Retrieved the template file url. " + fileURL ); return fileURL; } /** * Gets the Image node defined to hold the 'Map' and the node defined to hold the 'Legend'. * * @param root * @return List * @throws PrintMapServiceException */ private int[] parseImageNodes( Element root ) throws PrintMapServiceException { int[] mapParams = new int[4]; int mapCount = 0; try { List images = XMLTools.getRequiredNodes( root, "detail/band/image", null ); for ( int i = 0; i < images.size(); i++ ) { Node image = (Node) images.get( i ); // e.g. $P{MAP} String value = XMLTools.getRequiredNodeAsString( image, "imageExpression", null ); int idx = value.indexOf( "{" ); if ( idx != -1 ) { String tmp = value.substring( idx + 1, value.length() - 1 ); Element reportElement = (Element) XMLTools.getRequiredNode( image, "reportElement", null ); String width = reportElement.getAttribute( "width" ); String height = reportElement.getAttribute( "height" ); double res = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution(); // Templates created by iReport assumes a resolution of 72 dpi if ( tmp.startsWith( "MAP" ) ) { mapParams[0] = (int)( Integer.parseInt( width ) / 72d * res ); mapParams[1] = (int)( Integer.parseInt( height ) / 72d * res ); mapCount = mapCount + 1; } else if ( tmp.startsWith( "LEGEND" ) ) { mapParams[2] = (int)( Integer.parseInt( width ) / 72d * res ); mapParams[3] = (int)( Integer.parseInt( height ) / 72d * res ); } } } if ( ( mapCount == 0 ) || ( mapCount > 1 ) ) { throw new PrintMapServiceException( Messages.getMessage( "WMPS_TOO_MANY_MAPAREAS", mapCount ) ); } } catch ( XMLParsingException e ) { LOG.logError( e.getMessage(), e ); throw new PrintMapServiceException( Messages.getMessage( "WMPS_INVALID_JASPER_TEMPLATE" ) ); } return mapParams; } } /* ************************************************************************************************* Changes to this class. What the people have been up to: $Log: PrintMapHandler.java,v $ Revision 1.79 2006/11/27 09:07:52 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.78 2006/11/02 21:06:03 poth bug fix - scale calculation Revision 1.77 2006/10/17 20:31:19 poth *** empty log message *** Revision 1.76 2006/10/02 06:53:20 poth code enhancements Revision 1.75 2006/10/02 06:48:34 poth bug fixes Revision 1.74 2006/10/02 06:30:35 poth bug fixes Revision 1.73 2006/09/25 12:47:00 poth bug fixes - map scale calculation Revision 1.72 2006/09/22 12:38:08 mays ap: code revision and bugfixes Revision 1.71 2006/09/21 10:01:31 mays ap code revision Revision 1.70 2006/09/15 13:39:17 deshmukh mail link modified to support security servlet feature. Revision 1.69 2006/09/13 09:27:31 deshmukh changed template name handling Revision 1.68 2006/09/13 07:37:58 deshmukh removed excess debug statements. Revision 1.67 2006/09/11 16:08:41 mays add logging statements. cleaning up footer Revision 1.65 2006/09/05 15:30:12 deshmukh Revision 1.64 2006/09/04 13:19:43 deshmukh Revision 1.63 2006/09/04 11:32:25 deshmukh Revision 1.62 2006/08/31 10:29:51 deshmukh Revision 1.61 2006/08/24 08:01:13 deshmukh Revision 1.60 2006/08/24 06:42:16 poth Revision 1.59 2006/08/23 10:21:12 deshmukh Revision 1.58 2006/08/23 09:47:07 mays @ deshmukh: final response exception message bug fixed Revision 1.57 2006/08/10 07:32:45 deshmukh reset the pixel size from '0.00028' to '0.00035' Revision 1.56 2006/08/10 07:11:35 deshmukh WMPS has been modified to support the new configuration changes and the excess code not needed has been replaced. Revision 1.55 2006/08/02 06:51:29 deshmukh Revision 1.54 2006/08/01 14:20:10 deshmukh The wmps configuration has been modified and extended. Also fixed the javadoc. Revision 1.53 2006/08/01 13:41:48 deshmukh The wmps configuration has been modified and extended. Also fixed the javadoc. Revision 1.52 2006/08/01 07:39:46 deshmukh fixed bugs Revision 1.51 2006/07/31 11:21:07 deshmukh wmps implemention... Revision 1.50 2006/07/20 13:24:12 deshmukh Removed a few floating bugs. Revision 1.49 2006/07/13 12:24:45 poth adaptions required according to changes in org.deegree.ogcwebservice.wms.operations.GetMap Revision 1.48 2006/07/12 14:46:16 poth comment footer added ************************************************************************************************* */