001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wmps/DefaultGetMapHandler.java $
002 /*----------------------------------------------------------------------------
003 This file is part of deegree, http://deegree.org/
004 Copyright (C) 2001-2009 by:
005 Department of Geography, University of Bonn
006 and
007 lat/lon GmbH
008
009 This library is free software; you can redistribute it and/or modify it under
010 the terms of the GNU Lesser General Public License as published by the Free
011 Software Foundation; either version 2.1 of the License, or (at your option)
012 any later version.
013 This library is distributed in the hope that it will be useful, but WITHOUT
014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
016 details.
017 You should have received a copy of the GNU Lesser General Public License
018 along with this library; if not, write to the Free Software Foundation, Inc.,
019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
020
021 Contact information:
022
023 lat/lon GmbH
024 Aennchenstr. 19, 53177 Bonn
025 Germany
026 http://lat-lon.de/
027
028 Department of Geography, University of Bonn
029 Prof. Dr. Klaus Greve
030 Postfach 1147, 53001 Bonn
031 Germany
032 http://www.geographie.uni-bonn.de/deegree/
033
034 e-mail: info@deegree.org
035 ----------------------------------------------------------------------------*/
036 package org.deegree.ogcwebservices.wmps;
037
038 import java.awt.Color;
039 import java.awt.Font;
040 import java.awt.Graphics;
041 import java.awt.Graphics2D;
042 import java.awt.RenderingHints;
043 import java.awt.image.BufferedImage;
044 import java.util.ArrayList;
045 import java.util.List;
046
047 import org.deegree.framework.log.ILogger;
048 import org.deegree.framework.log.LoggerFactory;
049 import org.deegree.framework.util.CharsetUtils;
050 import org.deegree.framework.util.ImageUtils;
051 import org.deegree.framework.util.MapUtils;
052 import org.deegree.framework.util.StringTools;
053 import org.deegree.framework.xml.XMLParsingException;
054 import org.deegree.graphics.MapFactory;
055 import org.deegree.graphics.MapView;
056 import org.deegree.graphics.Theme;
057 import org.deegree.graphics.optimizers.LabelOptimizer;
058 import org.deegree.graphics.sld.AbstractLayer;
059 import org.deegree.graphics.sld.AbstractStyle;
060 import org.deegree.graphics.sld.NamedLayer;
061 import org.deegree.graphics.sld.NamedStyle;
062 import org.deegree.graphics.sld.SLDFactory;
063 import org.deegree.graphics.sld.StyledLayerDescriptor;
064 import org.deegree.graphics.sld.UserLayer;
065 import org.deegree.graphics.sld.UserStyle;
066 import org.deegree.i18n.Messages;
067 import org.deegree.model.crs.CRSFactory;
068 import org.deegree.model.crs.CoordinateSystem;
069 import org.deegree.model.crs.GeoTransformer;
070 import org.deegree.model.spatialschema.Envelope;
071 import org.deegree.model.spatialschema.Geometry;
072 import org.deegree.model.spatialschema.GeometryFactory;
073 import org.deegree.ogcbase.InvalidSRSException;
074 import org.deegree.ogcwebservices.OGCWebServiceException;
075 import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration;
076 import org.deegree.ogcwebservices.wmps.configuration.WMPSDeegreeParams;
077 import org.deegree.ogcwebservices.wmps.operation.PrintMap;
078 import org.deegree.ogcwebservices.wms.LayerNotDefinedException;
079 import org.deegree.ogcwebservices.wms.StyleNotDefinedException;
080 import org.deegree.ogcwebservices.wms.capabilities.Layer;
081 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
082 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
083 import org.deegree.ogcwebservices.wms.operation.GetMap;
084
085 /**
086 * This is a copy of the WMS package.
087 *
088 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
089 * @author last edited by: $Author: apoth $
090 *
091 * @version $Revision: 21104 $, $Date: 2009-11-27 14:33:04 +0100 (Fr, 27. Nov 2009) $
092 */
093 public class DefaultGetMapHandler implements GetMapHandler {
094
095 private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
096
097 protected GetMap request;
098
099 private Object[] themes;
100
101 protected double scale = 0;
102
103 private int count = 0;
104
105 protected CoordinateSystem reqCRS;
106
107 private WMPSConfiguration configuration;
108
109 private BufferedImage copyrightImg;
110
111 private Graphics graph;
112
113 /**
114 * Creates a new GetMapHandler object.
115 *
116 * @param configuration
117 * @param request
118 * request to perform
119 * @throws OGCWebServiceException
120 */
121 public DefaultGetMapHandler( WMPSConfiguration configuration, GetMap request ) throws OGCWebServiceException {
122 this.request = request;
123 this.configuration = configuration;
124
125 try {
126 // get copyright image if possible
127 this.copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyright() );
128 } catch ( Exception e ) {
129 // eat it
130 }
131
132 try {
133 this.reqCRS = CRSFactory.create( this.request.getSrs() );
134 } catch ( Exception e ) {
135 throw new InvalidSRSException( "SRS: " + request.getSrs() + "is nor known by the deegree WMS" );
136 }
137
138 }
139
140 /**
141 * returns the configuration used by the handler
142 *
143 * @return WMPSConfiguration
144 */
145 public WMPSConfiguration getConfiguration() {
146 return this.configuration;
147 }
148
149 /**
150 * increases the counter variable that holds the number of services that has sent a response. All data are available
151 * if the counter value equals the number of requested layers.
152 */
153 protected synchronized void increaseCounter() {
154 this.count++;
155 }
156
157 /**
158 * performs a GetMap request and returns the result encapsulated within a <tt>GetMapResult</tt> object.
159 * <p>
160 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
161 * impossible to return a result. If something went wrong performing the request (none fatal error) The exception
162 * shall be encapsulated within the response object to be returned to the client as requested (GetMap-Request
163 * EXCEPTION-Parameter).
164 *
165 * @param printMap
166 * @param g
167 * @throws OGCWebServiceException
168 */
169 public void performGetMap( PrintMap printMap, Graphics g )
170 throws OGCWebServiceException {
171
172 this.graph = g;
173
174 try {
175 CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
176 double dpi = printMap.getDpi();
177 if ( dpi < 0 ) {
178 dpi = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
179 }
180 double pixelSize = WMPSConfiguration.INCH2M / dpi;
181 this.scale = MapUtils.calcScale( this.request.getWidth(), this.request.getHeight(),
182 this.request.getBoundingBox(), crs, pixelSize );
183 LOG.logInfo( "OGC WMPS scale denominator: " + this.scale );
184 } catch ( Exception e ) {
185 LOG.logDebug( "-", e );
186 throw new OGCWebServiceException( "Couldn't calculate scale! " + e );
187 }
188
189 StyledLayerDescriptor sld = null;
190 try {
191 sld = toSLD( this.request.getLayers(), this.request.getStyledLayerDescriptor() );
192 } catch ( XMLParsingException e1 ) {
193 // should never happen
194 e1.printStackTrace();
195 }
196
197 AbstractLayer[] layers = sld.getLayers();
198 // get the number of themes assigned to the selected layers
199 // notice that there maybe more themes as there are layers because
200 // 1 .. n datasources can be assigned to one layer.
201 int cntTh = countNumberOfThemes( layers, this.scale );
202 this.themes = new Object[cntTh];
203 // invokes the data supplyer for each layer in an independent thread
204 int kk = 0;
205 for ( int i = 0; i < layers.length; i++ ) {
206 if ( layers[i] instanceof NamedLayer ) {
207 String styleName = null;
208 if ( i < this.request.getLayers().length ) {
209 styleName = this.request.getLayers()[i].getStyleName();
210 }
211 kk = invokeNamedLayer( layers[i], kk, styleName );
212 } else {
213 GetMapServiceInvokerForUL si = new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], kk++ );
214
215 si.start();
216 }
217 }
218 waitForFinished();
219 renderMap();
220
221 }
222
223 /**
224 * Invoke the named layer
225 *
226 * @param layer
227 * @param kk
228 * @param styleName
229 * @return int
230 * @throws OGCWebServiceException
231 */
232 private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName )
233 throws OGCWebServiceException {
234
235 Layer lay = this.configuration.getLayer( layer.getName() );
236
237 if ( validate( lay, layer.getName() ) ) {
238 UserStyle us = getStyles( (NamedLayer) layer, styleName );
239 AbstractDataSource[] ds = lay.getDataSource();
240
241 for ( int j = 0; j < ds.length; j++ ) {
242
243 ScaleHint scaleHint = ds[j].getScaleHint();
244 if ( this.scale >= scaleHint.getMin() && this.scale < scaleHint.getMax()
245 && isValidArea( ds[j].getValidArea() ) ) {
246 GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, lay, ds[j], us, kk++ );
247 si.start();
248 }
249 }
250 } else {
251 // set theme to null if no data are available for the requested
252 // area and/or scale
253 this.themes[kk++] = null;
254 increaseCounter();
255 }
256 return kk;
257 }
258
259 /**
260 * returns the number of <code>DataSource</code>s involved in a GetMap request
261 *
262 * @param layers
263 * @param currentscale
264 * @return int
265 */
266 private int countNumberOfThemes( AbstractLayer[] layers, double currentscale ) {
267 int cnt = 0;
268 for ( int i = 0; i < layers.length; i++ ) {
269 if ( layers[i] instanceof NamedLayer ) {
270 Layer lay = this.configuration.getLayer( layers[i].getName() );
271 AbstractDataSource[] ds = lay.getDataSource();
272 for ( int j = 0; j < ds.length; j++ ) {
273
274 ScaleHint scaleHint = ds[j].getScaleHint();
275 if ( currentscale >= scaleHint.getMin() && currentscale < scaleHint.getMax()
276 && isValidArea( ds[j].getValidArea() ) ) {
277
278 cnt++;
279 }
280 }
281 } else {
282 cnt++;
283 }
284 }
285 return cnt;
286 }
287
288 /**
289 * returns true if the requested boundingbox intersects with the valid area of a datasource
290 *
291 * @param validArea
292 * @return boolean
293 */
294 private boolean isValidArea( Geometry validArea ) {
295
296 if ( validArea != null ) {
297 try {
298 Envelope env = this.request.getBoundingBox();
299 Geometry geom = GeometryFactory.createSurface( env, this.reqCRS );
300 if ( !this.reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
301 // if requested CRS is not identical to the CRS of the valid area
302 // a transformation must be performed before intersection can
303 // be checked
304 GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
305 geom = gt.transform( geom );
306 }
307 return geom.intersects( validArea );
308 } catch ( Exception e ) {
309 // should never happen
310 LOG.logError( "could not validate WMS datasource area", e );
311 }
312 }
313 return true;
314 }
315
316 /**
317 * runs a loop until all sub requestes (one for each layer) has been finished or the maximum time limit has been
318 * exceeded.
319 *
320 * @throws OGCWebServiceException
321 */
322 private void waitForFinished()
323 throws OGCWebServiceException {
324 if ( this.count < this.themes.length ) {
325 // waits until the requested layers are available as <tt>DisplayElements</tt>
326 // or the time limit has been reached.
327 // if count == themes.length then no request must be performed
328 long timeStamp = System.currentTimeMillis();
329 long lapse = 0;
330 long timeout = 1000 * ( this.configuration.getDeegreeParams().getRequestTimeLimit() - 1 );
331 do {
332 try {
333 Thread.sleep( 50 );
334 lapse += 50;
335 } catch ( InterruptedException e ) {
336 throw new OGCWebServiceException( "GetMapHandler", "fatal exception waiting for "
337 + "GetMapHandler results" );
338 }
339 } while ( this.count < this.themes.length && lapse < timeout );
340 if ( System.currentTimeMillis() - timeStamp >= timeout ) {
341 throw new OGCWebServiceException( "Processing of the GetMap request " + "exceeds timelimit" );
342 }
343 }
344 }
345
346 /**
347 *
348 * @param layers
349 * @param inSLD
350 * @return StyledLayerDescriptor
351 * @throws XMLParsingException
352 */
353 private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
354 throws XMLParsingException {
355 StyledLayerDescriptor sld = null;
356
357 if ( layers != null && layers.length > 0 && inSLD == null ) {
358 // Adds the content from the LAYERS and STYLES attribute to the SLD
359 StringBuffer sb = new StringBuffer( 5000 );
360 sb.append( "<?xml version=\"1.0\" encoding=\"" + CharsetUtils.getSystemCharset() + "\"?>" );
361 sb.append( "<StyledLayerDescriptor version=\"1.0.0\" " );
362 sb.append( "xmlns='http://www.opengis.net/sld'>" );
363
364 for ( int i = 0; i < layers.length; i++ ) {
365 sb.append( "<NamedLayer>" );
366 sb.append( "<Name>" + layers[i].getName() + "</Name>" );
367 sb.append( "<NamedStyle><Name>" + layers[i].getStyleName() + "</Name></NamedStyle></NamedLayer>" );
368 }
369 sb.append( "</StyledLayerDescriptor>" );
370
371 try {
372 sld = SLDFactory.createSLD( sb.toString() );
373 } catch ( XMLParsingException e ) {
374 throw new XMLParsingException( StringTools.stackTraceToString( e ) );
375 }
376 } else if ( layers != null && layers.length > 0 && inSLD != null ) {
377 // if layers not null and sld is not null then SLD layers just be
378 // considered if present in the layers list
379 List<String> list = new ArrayList<String>();
380 for ( int i = 0; i < layers.length; i++ ) {
381 list.add( layers[i].getName() );
382 }
383
384 List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
385 AbstractLayer[] al = inSLD.getLayers();
386 for ( int i = 0; i < al.length; i++ ) {
387 if ( list.contains( al[i].getName() ) ) {
388 newList.add( al[i] );
389 }
390 }
391 al = new AbstractLayer[newList.size()];
392 sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
393 } else {
394 // if no layers are defined ...
395 sld = inSLD;
396 }
397
398 return sld;
399 }
400
401 /**
402 * returns the <tt>UserStyle</tt>s assigned to a named layer
403 *
404 * @param sldLayer
405 * layer to get the styles for
406 * @param styleName
407 * requested stylename (from the KVP encoding)
408 * @return UserStyle
409 * @throws OGCWebServiceException
410 */
411 private UserStyle getStyles( NamedLayer sldLayer, String styleName )
412 throws OGCWebServiceException {
413
414 AbstractStyle[] styles = sldLayer.getStyles();
415 UserStyle us = null;
416
417 // to avoid retrieving the layer again for each style
418 Layer layer = null;
419 layer = this.configuration.getLayer( sldLayer.getName() );
420 int i = 0;
421 while ( us == null && i < styles.length ) {
422 if ( styles[i] instanceof NamedStyle ) {
423 // styles will be taken from the WMS's style repository
424 us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
425 } else {
426 // if the requested style fits the name of the defined style or
427 // if the defined style is marked as default and the requested
428 // style if 'default' the condition is true. This includes that
429 // if more than one style with the same name or more than one
430 // style is marked as default always the first will be choosen
431 if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
432 || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
433 us = (UserStyle) styles[i];
434 }
435 }
436 i++;
437 }
438 if ( us == null ) {
439 // this may happens if the SLD contains a named layer but not
440 // a style! yes this is valid according to SLD spec 1.0.0
441 us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
442 }
443 return us;
444 }
445
446 /**
447 * Returns a Predifined UserStyle
448 *
449 * @param styleName
450 * @param layerName
451 * @param layer
452 * @return UserStyle
453 * @throws StyleNotDefinedException
454 */
455 private UserStyle getPredefinedStyle( String styleName, String layerName, Layer layer )
456 throws StyleNotDefinedException {
457 UserStyle us = null;
458
459 if ( "default".equals( styleName ) ) {
460 us = layer.getStyle( styleName );
461 }
462
463 if ( us == null ) {
464 if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
465 || styleName.equals( "default" ) ) {
466 styleName = "default:" + layerName;
467 }
468 }
469
470 us = layer.getStyle( styleName );
471 if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
472 String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
473 throw new StyleNotDefinedException( s );
474 }
475 return us;
476 }
477
478 /**
479 * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
480 * be thrown. If the layer matches the request, but isn't able to deviever data for the requested area and/or scale
481 * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
482 * true will be returned.
483 *
484 * @param layer
485 * layer as defined at the capabilities/configuration
486 * @param name
487 * name of the layer (must be submitted seperatly because the layer parameter can be <tt>null</tt>
488 * @return boolean
489 * @throws OGCWebServiceException
490 */
491 private boolean validate( Layer layer, String name )
492 throws OGCWebServiceException {
493
494 // check if layer is available
495 if ( layer == null ) {
496 throw new LayerNotDefinedException( "Layer: " + name + " is not known by the WMS" );
497 }
498
499 if ( !layer.isSrsSupported( this.request.getSrs() ) ) {
500 throw new InvalidSRSException( "SRS: " + this.request.getSrs() + "is not known by layer: " + name );
501 }
502
503 // check for valid coordinated reference system
504 String[] srs = layer.getSrs();
505 boolean tmp = false;
506 for ( int i = 0; i < srs.length; i++ ) {
507 if ( srs[i].equalsIgnoreCase( this.request.getSrs() ) ) {
508 tmp = true;
509 break;
510 }
511 }
512
513 if ( !tmp ) {
514 throw new InvalidSRSException( "layer: " + name + " can't be " + "delievered in SRS: "
515 + this.request.getSrs() );
516 }
517
518 // check bounding box
519 try {
520
521 Envelope bbox = this.request.getBoundingBox();
522 Envelope layerBbox = layer.getLatLonBoundingBox();
523 if ( !this.request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
524 // transform the bounding box of the request to EPSG:4326
525 GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
526 bbox = gt.transform( bbox, this.reqCRS );
527 }
528 if ( !bbox.intersects( layerBbox ) ) {
529 return false;
530 }
531
532 } catch ( Exception e ) {
533 LOG.logError( e.getMessage(), e );
534 throw new OGCWebServiceException( "couldn't compare bounding boxes\n" + e.toString() );
535 }
536
537 return true;
538 }
539
540 /**
541 * put a theme to the passed index of the themes array. The second param passed is a <tt>Theme</tt> or an exception
542 *
543 * @param index
544 * @param o
545 */
546 protected synchronized void putTheme( int index, Object o ) {
547 this.themes[index] = o;
548 }
549
550 /**
551 * renders the map from the <tt>DisplayElement</tt>s
552 */
553 private void renderMap() {
554
555 // GetMapResult response = null;
556 OGCWebServiceException exce = null;
557
558 ArrayList<Object> list = new ArrayList<Object>( 50 );
559 for ( int i = 0; i < this.themes.length; i++ ) {
560 if ( this.themes[i] instanceof Exception ) {
561 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", this.themes[i].toString() );
562 }
563 if ( this.themes[i] instanceof OGCWebServiceException ) {
564 exce = (OGCWebServiceException) this.themes[i];
565 break;
566 }
567 if ( this.themes[i] != null ) {
568 list.add( this.themes[i] );
569 }
570 }
571
572 if ( exce == null ) {
573 // only if no exception occured
574 try {
575 Theme[] th = list.toArray( new Theme[list.size()] );
576 MapView map = null;
577 if ( th.length > 0 ) {
578 double tr = configuration.getDeegreeParams().getPrintMapParam().getTargetResolution();
579 tr = WMPSConfiguration.INCH2M / tr;
580 map = MapFactory.createMapView( "deegree WMS", this.request.getBoundingBox(), this.reqCRS, th, tr );
581 }
582 this.graph.setClip( 0, 0, this.request.getWidth(), this.request.getHeight() );
583 if ( !this.request.getTransparency() ) {
584 this.graph.setColor( this.request.getBGColor() );
585 this.graph.fillRect( 0, 0, this.request.getWidth(), this.request.getHeight() );
586 }
587 if ( map != null ) {
588 Theme[] allthemes = map.getAllThemes();
589 map.addOptimizer( new LabelOptimizer( allthemes ) );
590 // antialiasing must be switched of for gif output format
591 // because the antialiasing may create more than 255 colors
592 // in the map/image, even just a few colors are defined in
593 // the styles
594 if ( !this.configuration.getDeegreeParams().isAntiAliased() ) {
595 ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
596 RenderingHints.VALUE_ANTIALIAS_ON );
597 ( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
598 RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
599 }
600 map.paint( this.graph );
601 }
602 } catch ( Exception e ) {
603 LOG.logError( e.getMessage(), e );
604 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
605 }
606 }
607
608 // print a copyright note at the left lower corner of the map
609 printCopyright( this.graph, this.request.getHeight() );
610
611 }
612
613 /**
614 * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
615 * capabilities/configuration
616 *
617 * @param g
618 * graphic context of the map
619 * @param heigth
620 * height of the map in pixel
621 */
622 private void printCopyright( Graphics g, int heigth ) {
623
624 WMPSDeegreeParams dp = this.configuration.getDeegreeParams();
625 String copyright = dp.getCopyright();
626 if ( this.copyrightImg != null ) {
627 g.drawImage( this.copyrightImg, 8, heigth - this.copyrightImg.getHeight() - 5, null );
628 } else {
629 if ( copyright != null ) {
630 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
631 g.setColor( Color.BLACK );
632 g.drawString( copyright, 8, heigth - 15 );
633 g.drawString( copyright, 10, heigth - 15 );
634 g.drawString( copyright, 8, heigth - 13 );
635 g.drawString( copyright, 10, heigth - 13 );
636 g.setColor( Color.WHITE );
637 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
638 g.drawString( copyright, 9, heigth - 14 );
639 // g.dispose();
640 }
641 }
642 }
643 }