001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wms/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.wms;
037
038 import static java.awt.image.BufferedImage.TYPE_BYTE_INDEXED;
039 import static java.util.Arrays.asList;
040 import static java.util.regex.Pattern.compile;
041 import static javax.media.jai.operator.ColorQuantizerDescriptor.MEDIANCUT;
042 import static org.deegree.crs.coordinatesystems.GeographicCRS.WGS84;
043 import static org.deegree.framework.util.CollectionUtils.find;
044 import static org.deegree.i18n.Messages.get;
045 import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetMapResponse;
046
047 import java.awt.Color;
048 import java.awt.Font;
049 import java.awt.Graphics;
050 import java.awt.Graphics2D;
051 import java.awt.RenderingHints;
052 import java.awt.image.BufferedImage;
053 import java.awt.image.IndexColorModel;
054 import java.awt.image.WritableRaster;
055 import java.util.ArrayList;
056 import java.util.HashMap;
057 import java.util.LinkedList;
058 import java.util.List;
059 import java.util.concurrent.Callable;
060 import java.util.concurrent.CancellationException;
061 import java.util.regex.Pattern;
062
063 import javax.media.jai.RenderedOp;
064 import javax.media.jai.operator.BandSelectDescriptor;
065 import javax.media.jai.operator.ColorQuantizerDescriptor;
066
067 import org.apache.batik.svggen.SVGGraphics2D;
068 import org.deegree.framework.concurrent.ExecutionFinishedEvent;
069 import org.deegree.framework.concurrent.Executor;
070 import org.deegree.framework.log.ILogger;
071 import org.deegree.framework.log.LoggerFactory;
072 import org.deegree.framework.util.ImageUtils;
073 import org.deegree.framework.util.MapUtils;
074 import org.deegree.framework.util.MimeTypeMapper;
075 import org.deegree.framework.util.CollectionUtils.Predicate;
076 import org.deegree.graphics.MapFactory;
077 import org.deegree.graphics.Theme;
078 import org.deegree.graphics.optimizers.LabelOptimizer;
079 import org.deegree.graphics.sld.AbstractLayer;
080 import org.deegree.graphics.sld.AbstractStyle;
081 import org.deegree.graphics.sld.NamedLayer;
082 import org.deegree.graphics.sld.NamedStyle;
083 import org.deegree.graphics.sld.StyledLayerDescriptor;
084 import org.deegree.graphics.sld.UserLayer;
085 import org.deegree.graphics.sld.UserStyle;
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.GeoTransformer;
090 import org.deegree.model.spatialschema.Envelope;
091 import org.deegree.model.spatialschema.Geometry;
092 import org.deegree.model.spatialschema.GeometryFactory;
093 import org.deegree.ogcbase.InvalidSRSException;
094 import org.deegree.ogcwebservices.InconsistentRequestException;
095 import org.deegree.ogcwebservices.InvalidParameterValueException;
096 import org.deegree.ogcwebservices.OGCWebServiceException;
097 import org.deegree.ogcwebservices.OGCWebServiceResponse;
098 import org.deegree.ogcwebservices.wms.GetMapServiceInvokerForNL.WMSExceptionFromWCS;
099 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
100 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
101 import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource;
102 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType;
103 import org.deegree.ogcwebservices.wms.configuration.WMSConfiguration_1_3_0;
104 import org.deegree.ogcwebservices.wms.configuration.WMSDeegreeParams;
105 import org.deegree.ogcwebservices.wms.operation.GetMap;
106 import org.deegree.ogcwebservices.wms.operation.GetMapResult;
107 import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory;
108 import org.deegree.ogcwebservices.wms.operation.GetMap.Layer;
109 import org.w3c.dom.Element;
110
111 /**
112 *
113 *
114 * @version $Revision: 18195 $
115 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
116 */
117 public class DefaultGetMapHandler implements GetMapHandler {
118
119 private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
120
121 private GetMap request = null;
122
123 // private Object[] themes = null;
124
125 private double scale = 0;
126
127 private CoordinateSystem reqCRS = null;
128
129 private WMSConfigurationType configuration = null;
130
131 private BufferedImage copyrightImg = null;
132
133 boolean version130 = false;
134
135 HashMap<String, String> sqls;
136
137 private GeoTransformer transformToWGS84;
138
139 // could be improved, I'm sure
140 private static final Pattern CHECKSQL = compile( "(^insert|^update|^delete|.*[ ()]insert[() ]|.*[() ]update[() ]|.*[() ]delete[() ]).*" );
141
142 /**
143 * Creates a new GetMapHandler object.
144 *
145 * @param configuration
146 * @param request
147 * request to perform
148 */
149 public DefaultGetMapHandler( WMSConfigurationType configuration, GetMap request ) {
150 this.request = request;
151 this.configuration = configuration;
152
153 try {
154 // get copyright image if possible
155 copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyRight() );
156 } catch ( Exception e ) {
157 // don't use copyright
158 }
159
160 }
161
162 /**
163 * returns the configuration used by the handler
164 *
165 * @return the configuration document
166 */
167 public WMSConfigurationType getConfiguration() {
168 return configuration;
169 }
170
171 /**
172 * performs a GetMap request and returns the result encapsulated within a <tt>GetMapResult</tt> object.
173 * <p>
174 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it
175 * impossible to return a result. If something went wrong performing the request (none fatal error) The exception
176 * shall be encapsulated within the response object to be returned to the client as requested (GetMap-Request
177 * EXCEPTION-Parameter).
178 *
179 * @return response to the GetMap response
180 */
181 public OGCWebServiceResponse performGetMap()
182 throws OGCWebServiceException {
183
184 // get templates, check templates
185 String sqltemplates = request.getVendorSpecificParameter( "SQLTEMPLATES" );
186 if ( sqltemplates != null ) {
187 LinkedList<String> sqls = new LinkedList<String>();
188 sqls.addAll( asList( sqltemplates.split( ";" ) ) );
189 if ( sqls.size() != request.getLayers().length ) {
190 throw new InvalidParameterValueException( get( "WMS_INVALID_SQL_TEMPLATE_NUMBER" ) );
191 }
192 this.sqls = new HashMap<String, String>( sqls.size() );
193 for ( int i = 0; i < request.getLayers().length; ++i ) {
194 String sql = sqls.peek();
195 if ( !sql.equals( "default" ) ) {
196 if ( CHECKSQL.matcher( sql.toLowerCase() ).matches() ) {
197 throw new InvalidParameterValueException(
198 get( "WMS_INVALID_SQL_TEMPLATE_NO_TRANSACTION_PLEASE" ) );
199 }
200 String name = request.getLayers()[i].getName();
201 // ok iff all database data sources have custom sql allowed
202 for ( AbstractDataSource ds : configuration.getLayer( name ).getDataSource() ) {
203 if ( ds instanceof DatabaseDataSource ) {
204 if ( !( (DatabaseDataSource) ds ).isCustomSQLAllowed() ) {
205 throw new InvalidParameterValueException(
206 get(
207 "WMS_SQL_TEMPLATE_NOT_ALLOWED_FOR_LAYER",
208 name ) );
209 }
210 }
211 }
212
213 this.sqls.put( name, sqls.poll() );
214 }
215 }
216 }
217
218 List<Callable<Object>> themes = constructThemes();
219
220 Executor executor = Executor.getInstance();
221 try {
222 List<ExecutionFinishedEvent<Object>> results;
223 results = executor.performSynchronously( themes, configuration.getDeegreeParams().getRequestTimeLimit() );
224
225 GetMapResult res = renderMap( results );
226 return res;
227 } catch ( InterruptedException e ) {
228 LOG.logError( e.getMessage(), e );
229 String s = Messages.getMessage( "WMS_WAITING" );
230 throw new OGCWebServiceException( getClass().getName(), s );
231 }
232 }
233
234 /**
235 * @return a list of callables that construct the maps to be painted
236 * @throws OGCWebServiceException
237 */
238 public List<Callable<Object>> constructThemes()
239 throws OGCWebServiceException {
240 // some initialization is done here because the constructor is called by reflection
241 // and the exceptions won't be properly handled in that case
242 if ( reqCRS == null ) {
243 try {
244 reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
245 } catch ( Exception e ) {
246 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
247 }
248 }
249
250 version130 = "1.3.0".equals( request.getVersion() );
251
252 // exceeds the max allowed map width ?
253 int maxWidth = configuration.getDeegreeParams().getMaxMapWidth();
254 if ( ( maxWidth != 0 ) && ( request.getWidth() > maxWidth ) ) {
255 throw new InconsistentRequestException( Messages.getMessage( "WMS_EXCEEDS_WIDTH", new Integer( maxWidth ) ) );
256 }
257
258 // exceeds the max allowed map height ?
259 int maxHeight = configuration.getDeegreeParams().getMaxMapHeight();
260 if ( ( maxHeight != 0 ) && ( request.getHeight() > maxHeight ) ) {
261 throw new InconsistentRequestException(
262 Messages.getMessage( "WMS_EXCEEDS_HEIGHT", new Integer( maxHeight ) ) );
263 }
264
265 try {
266 double pixelSize = 1;
267 if ( version130 ) {
268 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
269 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
270 pixelSize = MapUtils.DEFAULT_PIXEL_SIZE;
271 }
272
273 scale = MapUtils.calcScale( request.getWidth(), request.getHeight(), request.getBoundingBox(), reqCRS,
274 pixelSize );
275
276 LOG.logInfo( "OGC WMS scale: " + scale );
277 } catch ( Exception e ) {
278 LOG.logError( e.getMessage(), e );
279 throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC" ) );
280 }
281
282 Layer[] ls = request.getLayers();
283
284 // if 1.3.0, check for maximum allowed layers
285 if ( version130 ) {
286 WMSConfiguration_1_3_0 cfg = (WMSConfiguration_1_3_0) configuration;
287 if ( ls.length > cfg.getLayerLimit() ) {
288 String ms = Messages.getMessage( "WMS_EXCEEDS_NUMBER", new Integer( cfg.getLayerLimit() ) );
289 throw new InconsistentRequestException( ms );
290 }
291 }
292
293 Layer[] oldLayers = ls;
294 ls = validateLayers( ls );
295
296 LOG.logDebug( "Validated " + ls.length + " layers." );
297
298 StyledLayerDescriptor sld = toSLD( oldLayers, request.getStyledLayerDescriptor() );
299
300 AbstractLayer[] layers = sld.getLayers();
301
302 LOG.logDebug( "After SLD consideration, found " + layers.length + " layers." );
303
304 Envelope wgs84bbox = request.getBoundingBox();
305 if ( !request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
306 // transform the bounding box of the request to EPSG:4326
307 transformToWGS84 = new GeoTransformer( CRSFactory.create( WGS84 ) );
308 try {
309 wgs84bbox = transformToWGS84.transform( wgs84bbox, reqCRS );
310 } catch ( Exception e ) {
311 // should never happen
312 LOG.logError( "Could not validate WMS datasource area", e );
313 }
314
315 }
316
317 List<Callable<Object>> themes = new LinkedList<Callable<Object>>();
318 for ( int i = 0; i < layers.length; i++ ) {
319
320 if ( layers[i] instanceof NamedLayer ) {
321 String styleName = null;
322 if ( i < request.getLayers().length ) {
323 styleName = request.getLayers()[i].getStyleName();
324 }
325 invokeNamedLayer( layers[i], styleName, themes, wgs84bbox );
326 } else {
327 double sc = scale;
328 if ( !version130 ) {
329 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator
330 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter
331 sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
332 }
333 themes.add( new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], sc ) );
334 }
335 }
336
337 return themes;
338 }
339
340 /**
341 * this methods validates layer in two ways:<br>
342 * a) are layers available from the current WMS<br>
343 * b) If a layer is selected that includes other layers determine all its sublayers having <Name>s and return them
344 * instead
345 *
346 * @param ls
347 * @return the layers
348 * @throws LayerNotDefinedException
349 * @throws InvalidSRSException
350 */
351 private Layer[] validateLayers( Layer[] ls )
352 throws LayerNotDefinedException, InvalidSRSException {
353
354 List<Layer> layer = new ArrayList<Layer>( ls.length );
355 for ( int i = 0; i < ls.length; i++ ) {
356 org.deegree.ogcwebservices.wms.capabilities.Layer l = configuration.getLayer( ls[i].getName() );
357
358 if ( l == null ) {
359 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", ls[i].getName() ) );
360 }
361
362 validateSRS( l.getSrs(), ls[i].getName() );
363
364 layer.add( ls[i] );
365 if ( l.getLayer() != null ) {
366 layer = addNestedLayers( l.getLayer(), ls[i].getStyleName(), layer );
367 }
368 }
369
370 return layer.toArray( new Layer[layer.size()] );
371 }
372
373 /**
374 * adds all direct and none direct sub-layers of the passed WMS capabilities layer as
375 *
376 * @see GetMap.Layer to the passed list.
377 * @param list
378 * @return all sublayers
379 * @throws InvalidSRSException
380 */
381 private List<Layer> addNestedLayers( org.deegree.ogcwebservices.wms.capabilities.Layer[] ll, String styleName,
382 List<Layer> list )
383 throws InvalidSRSException {
384
385 for ( int j = 0; j < ll.length; j++ ) {
386 if ( ll[j].getName() != null ) {
387 String name = ll[j].getName();
388 validateSRS( ll[j].getSrs(), name );
389 list.add( GetMap.createLayer( name, styleName ) );
390 }
391 if ( ll[j].getLayer() != null ) {
392 list = addNestedLayers( ll[j].getLayer(), styleName, list );
393 }
394
395 }
396 return list;
397 }
398
399 /**
400 * throws an exception if the requested SRS is not be supported by the passed layer (name)
401 *
402 * @param srs
403 * @param name
404 * @throws InvalidSRSException
405 */
406 private void validateSRS( String[] srs, String name )
407 throws InvalidSRSException {
408 boolean validSRS = false;
409 for ( int k = 0; k < srs.length; k++ ) {
410 validSRS = srs[k].equalsIgnoreCase( reqCRS.getIdentifier() );
411 if ( validSRS )
412 break;
413 }
414 if ( !validSRS ) {
415 String s = Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", reqCRS.getIdentifier(), name );
416 throw new InvalidSRSException( s );
417 }
418 }
419
420 private void invokeNamedLayer( AbstractLayer layer, String styleName, List<Callable<Object>> tasks,
421 Envelope wgs84bbox )
422 throws OGCWebServiceException {
423 org.deegree.ogcwebservices.wms.capabilities.Layer lay = configuration.getLayer( layer.getName() );
424
425 LOG.logDebug( "Invoked layer " + layer.getName() );
426 if ( validate( lay, layer.getName(), wgs84bbox ) ) {
427 UserStyle us = getStyles( (NamedLayer) layer, styleName );
428 AbstractDataSource[] ds = lay.getDataSource();
429
430 if ( ds.length == 0 ) {
431 LOG.logDebug( "No datasources for layer " + layer.getName() );
432 } else {
433 for ( int j = 0; j < ds.length; j++ ) {
434
435 LOG.logDebug( "Invoked datasource " + ds[j].getClass() + " for layer " + layer.getName() );
436
437 ScaleHint scaleHint = ds[j].getScaleHint();
438 if ( scale >= scaleHint.getMin() && scale < scaleHint.getMax()
439 && isValidArea( ds[j].getValidArea() ) ) {
440 double sc = scale;
441 if ( !version130 ) {
442 // required because for WMS 1.3.0 'scale' represents the
443 // ScaleDenominator
444 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in
445 // meter
446 sc = scale / MapUtils.DEFAULT_PIXEL_SIZE;
447 }
448 GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, (NamedLayer) layer, ds[j],
449 us, sc );
450 tasks.add( si );
451 } else {
452 LOG.logDebug( "Not showing layer " + layer.getName() + " due to scale" );
453 }
454 }
455 }
456 } else {
457 // using side effects for everything is great:
458 // when layers are eg. out of the bounding box, the use of invalid styles was not checked
459 // so let's do it here...
460 getStyles( (NamedLayer) layer, styleName );
461 }
462 }
463
464 /**
465 * returns true if the requested boundingbox intersects with the valid area of a datasource
466 *
467 * @param validArea
468 */
469 private boolean isValidArea( Geometry validArea ) {
470
471 if ( validArea != null ) {
472 try {
473 Envelope env = request.getBoundingBox();
474 Geometry geom = GeometryFactory.createSurface( env, reqCRS );
475 if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) {
476 // if requested CRS is not identical to the CRS of the valid area
477 // a transformation must be performed before intersection can
478 // be checked
479 GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
480 geom = gt.transform( geom );
481 }
482 return geom.intersects( validArea );
483 } catch ( Exception e ) {
484 // should never happen
485 LOG.logError( "Could not validate WMS datasource area", e );
486 }
487 }
488 return true;
489 }
490
491 /**
492 * creates a StyledLayerDocument containing all requested layer, nested layers if required and assigend styles. Not
493 * considered are nested layers for mixed requests (LAYERS- and SLD(_BODY)- parameter has been defined)
494 *
495 * @param layers
496 * @param inSLD
497 * @return a combined SLD object
498 * @throws InvalidSRSException
499 */
500 private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
501 throws InvalidSRSException {
502 StyledLayerDescriptor sld = null;
503
504 if ( layers != null && layers.length > 0 && inSLD == null ) {
505 // if just a list of layers has been requested
506
507 // create a SLD from the requested LAYERS and assigned STYLES
508 List<AbstractLayer> al = new ArrayList<AbstractLayer>( layers.length * 2 );
509 for ( int i = 0; i < layers.length; i++ ) {
510 AbstractStyle[] as = new AbstractStyle[] { new NamedStyle( layers[i].getStyleName() ) };
511 al.add( new NamedLayer( layers[i].getName(), null, as ) );
512
513 // collect all named nested layers
514 org.deegree.ogcwebservices.wms.capabilities.Layer lla;
515 lla = configuration.getLayer( layers[i].getName() );
516 List<GetMap.Layer> list = new ArrayList<GetMap.Layer>();
517 addNestedLayers( lla.getLayer(), layers[i].getStyleName(), list );
518
519 // add nested layers to list of layers to be handled
520 for ( int j = 0; j < list.size(); j++ ) {
521 GetMap.Layer nestedLayer = list.get( j );
522 as = new AbstractStyle[] { new NamedStyle( nestedLayer.getStyleName() ) };
523 al.add( new NamedLayer( nestedLayer.getName(), null, as ) );
524 }
525 }
526 sld = new StyledLayerDescriptor( al.toArray( new AbstractLayer[al.size()] ), "1.0.0" );
527 } else if ( layers != null && layers.length > 0 && inSLD != null ) {
528 // if layers not null and sld is not null then SLD layers just be
529 // considered if present in the layers list
530 // TODO
531 // layer with nested layers are not handled correctly and I think
532 // it really causes a lot of problems to use them in such a way
533 // because the style assigned to the mesting layer must be
534 // applicable for all nested layers.
535 List<String> list = new ArrayList<String>();
536 for ( int i = 0; i < layers.length; i++ ) {
537 list.add( layers[i].getName() );
538 }
539
540 List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
541
542 for ( final GetMap.Layer lay : layers ) {
543 NamedLayer sldLay = find( inSLD.getNamedLayers(), new Predicate<NamedLayer>() {
544 public boolean eval( NamedLayer t ) {
545 return t.getName().equals( lay.getName() );
546 }
547 } );
548
549 AbstractStyle[] as;
550 if ( sldLay == null ) {
551 as = new AbstractStyle[] { new NamedStyle( lay.getStyleName() ) };
552 newList.add( new NamedLayer( lay.getName(), null, as ) );
553 } else {
554 newList.add( sldLay );
555 }
556
557 // finally, don't forget the user layers
558 for ( UserLayer ul : inSLD.getUserLayers() ) {
559 newList.add( ul );
560 }
561 }
562
563 AbstractLayer[] al = new AbstractLayer[newList.size()];
564 sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
565 } else {
566 // if no layers but a SLD is defined ...
567 AbstractLayer[] as = inSLD.getLayers();
568 for ( AbstractLayer l : as ) {
569 addNestedLayers( l, inSLD );
570 }
571
572 sld = inSLD;
573 }
574
575 return sld;
576 }
577
578 // adds the nested layers to the sld
579 private void addNestedLayers( AbstractLayer l, StyledLayerDescriptor sld ) {
580 if ( !( l instanceof NamedLayer ) ) {
581 return;
582 }
583 if ( configuration.getLayer( l.getName() ) == null ) {
584 return;
585 }
586
587 org.deegree.ogcwebservices.wms.capabilities.Layer[] ls;
588 ls = configuration.getLayer( l.getName() ).getLayer();
589 for ( org.deegree.ogcwebservices.wms.capabilities.Layer lay : ls ) {
590 NamedStyle sty = new NamedStyle( lay.getStyles()[0].getName() );
591 AbstractStyle[] newSty = new AbstractStyle[] { sty };
592 NamedLayer newLay = new NamedLayer( lay.getName(), null, newSty );
593 sld.addLayer( newLay );
594 }
595 }
596
597 /**
598 * returns the <tt>UserStyle</tt>s assigned to a named layer
599 *
600 * @param sldLayer
601 * layer to get the styles for
602 * @param styleName
603 * requested stylename (from the KVP encoding)
604 */
605 private UserStyle getStyles( NamedLayer sldLayer, String styleName )
606 throws OGCWebServiceException {
607
608 AbstractStyle[] styles = sldLayer.getStyles();
609 UserStyle us = null;
610
611 // to avoid retrieving the layer again for each style
612 org.deegree.ogcwebservices.wms.capabilities.Layer layer = null;
613 layer = configuration.getLayer( sldLayer.getName() );
614 int i = 0;
615 while ( us == null && i < styles.length ) {
616 if ( styles[i] instanceof NamedStyle ) {
617 // styles will be taken from the WMS's style repository
618 us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
619 } else {
620 // if the requested style fits the name of the defined style or
621 // if the defined style is marked as default and the requested
622 // style if 'default' the condition is true. This includes that
623 // if more than one style with the same name or more than one
624 // style is marked as default always the first will be choosen
625 if ( styleName == null || ( styles[i].getName() != null && styles[i].getName().equals( styleName ) )
626 || ( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
627 us = (UserStyle) styles[i];
628 }
629 }
630 i++;
631 }
632 if ( us == null ) {
633 // this may happens if the SLD contains a named layer but not
634 // a style! yes this is valid according to SLD spec 1.0.0
635 us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
636 }
637 return us;
638 }
639
640 /**
641 *
642 * @param styleName
643 * @param layerName
644 * @param layer
645 * @return the style
646 * @throws StyleNotDefinedException
647 */
648 public UserStyle getPredefinedStyle( String styleName, String layerName,
649 org.deegree.ogcwebservices.wms.capabilities.Layer layer )
650 throws StyleNotDefinedException {
651 UserStyle us = null;
652 if ( "default".equals( styleName ) ) {
653 us = layer.getStyle( styleName );
654 }
655
656 if ( us == null ) {
657 if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
658 || styleName.equals( "default" ) ) {
659 styleName = "default:" + layerName;
660 }
661 }
662
663 us = layer.getStyle( styleName );
664
665 if ( us == null && !( styleName.startsWith( "default" ) ) && !( styleName.startsWith( "$DEFAULT" ) ) ) {
666 String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
667 throw new StyleNotDefinedException( s );
668 }
669 return us;
670 }
671
672 /**
673 * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will
674 * be thrown. If the layer matches the request, but isn't able to deviever data for the requested area and/or scale
675 * false will be returned. If the layer matches the request and contains data for the requested area and/or scale
676 * true will be returned.
677 *
678 * @param layer
679 * layer as defined at the capabilities/configuration
680 * @param name
681 * name of the layer (must be submitted separately because the layer parameter can be <tt>null</tt>
682 * @param wgs84bbox
683 * the wgs84 bbox of the request
684 */
685 private boolean validate( org.deegree.ogcwebservices.wms.capabilities.Layer layer, String name, Envelope wgs84bbox )
686 throws OGCWebServiceException {
687
688 // check if layer is available
689 if ( layer == null ) {
690 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", name ) );
691 }
692
693 // check bounding box
694 try {
695 Envelope layerBbox = layer.getLatLonBoundingBox();
696 if ( !wgs84bbox.intersects( layerBbox ) ) {
697 LOG.logDebug( "Not showing layer because the request is out of the bounding box." );
698 return false;
699 }
700
701 } catch ( Exception e ) {
702 LOG.logError( e.getMessage(), e );
703 throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) );
704 }
705
706 return true;
707 }
708
709 /**
710 * renders the map from the <tt>DisplayElement</tt>s
711 *
712 * @param results
713 * @return a result object suitable for further processing
714 *
715 * @throws OGCWebServiceException
716 */
717 public GetMapResult renderMap( List<ExecutionFinishedEvent<Object>> results )
718 throws OGCWebServiceException {
719
720 OGCWebServiceException exce = null;
721
722 ArrayList<Object> list = new ArrayList<Object>( 50 );
723 for ( ExecutionFinishedEvent<Object> evt : results ) {
724 Object o = null;
725
726 // exception handling might be handled in a better way
727 try {
728 o = evt.getResult();
729 } catch ( CancellationException e ) {
730 exce = new OGCWebServiceException( getClass().getName(), e.toString() );
731 } catch ( OGCWebServiceException e ) {
732 throw e;
733 } catch ( Throwable e ) {
734 exce = new OGCWebServiceException( getClass().getName(), e.toString() );
735 }
736
737 if ( o instanceof WMSExceptionFromWCS ) {
738 if ( results.size() == 1 ) {
739 exce = ( (WMSExceptionFromWCS) o ).wrapped;
740 o = exce;
741 } else {
742 o = null;
743 }
744 }
745 if ( o instanceof Exception ) {
746 exce = new OGCWebServiceException( getClass().getName(), o.toString() );
747 }
748 if ( o instanceof OGCWebServiceException ) {
749 exce = (OGCWebServiceException) o;
750 break;
751 }
752 if ( o != null ) {
753 list.add( o );
754 }
755 }
756
757 return render( list.toArray( new Theme[list.size()] ), exce );
758 }
759
760 /**
761 * @param themes
762 * @param exce
763 * @return a result object suitable for further processing elsewhere
764 * @throws InvalidSRSException
765 */
766 public GetMapResult render( Theme[] themes, OGCWebServiceException exce )
767 throws InvalidSRSException {
768 // some initialization is done here because the constructor is called by reflection
769 // and the exceptions won't be properly handled in that case
770 // NOTE that it has to be repeated here in case someone wants to use this method only!
771 if ( reqCRS == null ) {
772 try {
773 reqCRS = CRSFactory.create( request.getSrs().toLowerCase() );
774 } catch ( Exception e ) {
775 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", request.getSrs() ) );
776 }
777 }
778
779 GetMapResult response = null;
780
781 String mime = MimeTypeMapper.toMimeType( request.getFormat() );
782
783 if ( configuration.getDeegreeParams().getDefaultPNGFormat() != null && mime.equalsIgnoreCase( "image/png" ) ) {
784 mime = configuration.getDeegreeParams().getDefaultPNGFormat();
785 }
786
787 // get target object for rendering
788 Object target = GraphicContextFactory.createGraphicTarget( mime, request.getWidth(), request.getHeight() );
789
790 // get graphic context of the target
791 Graphics g = GraphicContextFactory.createGraphicContext( mime, target );
792 if ( exce == null ) {
793 // only if no exception occured
794 try {
795 org.deegree.graphics.MapView map = null;
796 if ( themes.length > 0 ) {
797 map = MapFactory.createMapView( "deegree WMS", request.getBoundingBox(), reqCRS, themes,
798 MapUtils.DEFAULT_PIXEL_SIZE );
799 }
800 g.setClip( 0, 0, request.getWidth(), request.getHeight() );
801
802 if ( !request.getTransparency() ) {
803 if ( g instanceof Graphics2D ) {
804 // this ensures real clearing (rendering modifies the color ever so
805 // slightly)
806 ( (Graphics2D) g ).setBackground( request.getBGColor() );
807 g.clearRect( 0, 0, request.getWidth(), request.getHeight() );
808 } else {
809 g.setColor( request.getBGColor() );
810 g.fillRect( 0, 0, request.getWidth(), request.getHeight() );
811 }
812 }
813
814 if ( map != null ) {
815 Theme[] thms = map.getAllThemes();
816 map.addOptimizer( new LabelOptimizer( thms ) );
817 // antialiasing must be switched of for gif output format
818 // because the antialiasing may create more than 255 colors
819 // in the map/image, even just a few colors are defined in
820 // the styles
821 if ( !request.getFormat().equalsIgnoreCase( "image/gif" ) ) {
822 if ( configuration.getDeegreeParams().isAntiAliased() ) {
823 ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
824 RenderingHints.VALUE_ANTIALIAS_ON );
825 ( (Graphics2D) g ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
826 RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
827 }
828 }
829 map.paint( g );
830 }
831 } catch ( Exception e ) {
832 LOG.logError( e.getMessage(), e );
833 exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
834 }
835 }
836
837 // print a copyright note at the left lower corner of the map
838 printCopyright( g, request.getHeight() );
839
840 if ( mime.equals( "image/svg+xml" ) || mime.equals( "image/svg xml" ) ) {
841 Element root = ( (SVGGraphics2D) g ).getRoot();
842 root.setAttribute( "xmlns:xlink", "http://www.w3.org/1999/xlink" );
843 response = WMSProtocolFactory.createGetMapResponse( request, exce, root );
844 } else {
845
846 BufferedImage img = (BufferedImage) target;
847
848 if ( mime.equals( "image/png; mode=8bit" ) ) {
849 RenderedOp torgb = BandSelectDescriptor.create( img, new int[] { 0, 1, 2 }, null );
850
851 torgb = ColorQuantizerDescriptor.create( torgb, MEDIANCUT, 254, null, null, null, null, null );
852
853 WritableRaster data = torgb.getAsBufferedImage().getRaster();
854
855 IndexColorModel model = (IndexColorModel) torgb.getColorModel();
856 byte[] reds = new byte[256];
857 byte[] greens = new byte[256];
858 byte[] blues = new byte[256];
859 byte[] alphas = new byte[256];
860 model.getReds( reds );
861 model.getGreens( greens );
862 model.getBlues( blues );
863 // note that this COULD BE OPTIMIZED to SUPPORT EG HALF TRANSPARENT PIXELS for PNG-8!
864 // It's not true that PNG-8 does not support this! Try setting the value to eg. 128 here and see what
865 // you'll get...
866 for ( int i = 0; i < 254; ++i ) {
867 alphas[i] = -1;
868 }
869 alphas[255] = 0;
870 IndexColorModel newModel = new IndexColorModel( 8, 256, reds, greens, blues, alphas );
871
872 // yeah, double memory, but it was the only way I could find (I could be blind...)
873 BufferedImage res = new BufferedImage( torgb.getWidth(), torgb.getHeight(), TYPE_BYTE_INDEXED, newModel );
874 res.setData( data );
875
876 // do it the hard way as the OR operation would destroy the channels
877 for ( int y = 0; y < img.getHeight(); ++y ) {
878 for ( int x = 0; x < img.getWidth(); ++x ) {
879 if ( img.getRGB( x, y ) == 0 ) {
880 res.setRGB( x, y, 0 );
881 }
882 }
883 }
884
885 target = res;
886 }
887
888 response = createGetMapResponse( request, exce, target );
889 }
890 g.dispose();
891
892 return response;
893 }
894
895 // works, but only with some bogus pixel that means "transparency"
896 // private static BufferedImage makeTransparent( BufferedImage img ) {
897 // IndexColorModel cm = (IndexColorModel) img.getColorModel();
898 // WritableRaster raster = img.getRaster();
899 // int pixel = raster.getSample( 0, 0, 0 );
900 // int size = cm.getMapSize();
901 // byte[] reds = new byte[size];
902 // byte[] greens = new byte[size];
903 // byte[] blues = new byte[size];
904 // cm.getReds( reds );
905 // cm.getGreens( greens );
906 // cm.getBlues( blues );
907 // return new BufferedImage( new IndexColorModel( 8, size, reds, greens, blues, pixel ), raster,
908 // img.isAlphaPremultiplied(), null );
909 // }
910
911 /**
912 * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
913 * capabilities/configuration
914 *
915 * @param g
916 * graphic context of the map
917 * @param heigth
918 * height of the map in pixel
919 */
920 private void printCopyright( Graphics g, int heigth ) {
921 WMSDeegreeParams dp = configuration.getDeegreeParams();
922 String copyright = dp.getCopyRight();
923 if ( copyrightImg != null ) {
924 g.drawImage( copyrightImg, 8, heigth - copyrightImg.getHeight() - 5, null );
925 } else {
926 if ( copyright != null ) {
927 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
928 g.setColor( Color.BLACK );
929 g.drawString( copyright, 8, heigth - 15 );
930 g.drawString( copyright, 10, heigth - 15 );
931 g.drawString( copyright, 8, heigth - 13 );
932 g.drawString( copyright, 10, heigth - 13 );
933 g.setColor( Color.WHITE );
934 g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
935 g.drawString( copyright, 9, heigth - 14 );
936 }
937 }
938
939 }
940
941 /**
942 * @return the request that is being handled
943 */
944 protected GetMap getRequest() {
945 return request;
946 }
947
948 /**
949 * @return the requests coordinate system
950 */
951 protected CoordinateSystem getRequestCRS() {
952 return reqCRS;
953 }
954
955 }