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