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