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 }