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