//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/ogcwebservices/wmps/DefaultGetMapHandler.java,v 1.35 2006/11/29 13:00:54 schmitz Exp $
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
EXSE, Department of Geography, University of Bonn
http://www.giub.uni-bonn.de/deegree/
lat/lon GmbH
http://www.lat-lon.de
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Contact:
Andreas Poth
lat/lon GmbH
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Prof. Dr. Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: greve@giub.uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.ogcwebservices.wmps;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.CharsetUtils;
import org.deegree.framework.util.ImageUtils;
import org.deegree.framework.util.MapUtils;
import org.deegree.framework.util.StringTools;
import org.deegree.framework.xml.XMLParsingException;
import org.deegree.graphics.MapFactory;
import org.deegree.graphics.MapView;
import org.deegree.graphics.Theme;
import org.deegree.graphics.optimizers.LabelOptimizer;
import org.deegree.graphics.sld.AbstractLayer;
import org.deegree.graphics.sld.AbstractStyle;
import org.deegree.graphics.sld.NamedLayer;
import org.deegree.graphics.sld.NamedStyle;
import org.deegree.graphics.sld.SLDFactory;
import org.deegree.graphics.sld.StyledLayerDescriptor;
import org.deegree.graphics.sld.UserLayer;
import org.deegree.graphics.sld.UserStyle;
import org.deegree.i18n.Messages;
import org.deegree.model.crs.CRSFactory;
import org.deegree.model.crs.CoordinateSystem;
import org.deegree.model.crs.GeoTransformer;
import org.deegree.model.crs.IGeoTransformer;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.Geometry;
import org.deegree.model.spatialschema.GeometryFactory;
import org.deegree.ogcbase.InvalidSRSException;
import org.deegree.ogcwebservices.OGCWebServiceException;
import org.deegree.ogcwebservices.wmps.configuration.WMPSConfiguration;
import org.deegree.ogcwebservices.wmps.configuration.WMPSDeegreeParams;
import org.deegree.ogcwebservices.wms.LayerNotDefinedException;
import org.deegree.ogcwebservices.wms.StyleNotDefinedException;
import org.deegree.ogcwebservices.wms.capabilities.Layer;
import org.deegree.ogcwebservices.wms.capabilities.ScaleHint;
import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource;
import org.deegree.ogcwebservices.wms.operation.GetMap;
/**
* This is a copy of the WMS package.
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author: schmitz $
*
* @version $Revision: 1.35 $, $Date: 2006/11/29 13:00:54 $
*/
public class DefaultGetMapHandler implements GetMapHandler {
private static final ILogger LOG = LoggerFactory.getLogger( DefaultGetMapHandler.class );
protected GetMap request;
private Object[] themes;
protected double scale = 0;
private int count = 0;
protected CoordinateSystem reqCRS;
private WMPSConfiguration configuration;
private BufferedImage copyrightImg;
private Graphics graph;
/**
* Creates a new GetMapHandler object.
*
* @param configuration
* @param request
* request to perform
* @throws OGCWebServiceException
*/
public DefaultGetMapHandler( WMPSConfiguration configuration, GetMap request )
throws OGCWebServiceException {
this.request = request;
this.configuration = configuration;
try {
// get copyright image if possible
this.copyrightImg = ImageUtils.loadImage( configuration.getDeegreeParams().getCopyright() );
} catch ( Exception e ) {
}
try {
this.reqCRS = CRSFactory.create( this.request.getSrs() );
} catch ( Exception e ) {
throw new InvalidSRSException( "SRS: " + request.getSrs()
+ "is nor known by the deegree WMS" );
}
}
/**
* returns the configuration used by the handler
*
* @return WMPSConfiguration
*/
public WMPSConfiguration getConfiguration() {
return this.configuration;
}
/**
* increases the counter variable that holds the number of services that has sent a response.
* All data are available if the counter value equals the number of requested layers.
*/
protected synchronized void increaseCounter() {
this.count++;
}
/**
* performs a GetMap request and retruns the result encapsulated within a <tt>GetMapResult</tt>
* object.
* <p>
* The method throws an WebServiceException that only shall be thrown if an fatal error occurs
* that makes it imposible to return a result. If something wents wrong performing the request
* (none fatal error) The exception shall be encapsulated within the response object to be
* returned to the client as requested (GetMap-Request EXCEPTION-Parameter).
*
* @param g
* @throws OGCWebServiceException
*/
public void performGetMap( Graphics g )
throws OGCWebServiceException {
this.graph = g;
try {
CoordinateSystem crs = CRSFactory.create( this.request.getSrs() );
this.scale = MapUtils.calcScale( this.request.getWidth(), this.request.getHeight(),
this.request.getBoundingBox(), crs, 1 );
LOG.logInfo( "OGC WMS scale: " + this.scale );
} catch ( Exception e ) {
LOG.logDebug( "-", e );
throw new OGCWebServiceException( "Couldn't calculate scale! " + e );
}
StyledLayerDescriptor sld = null;
try {
sld = toSLD( this.request.getLayers(), this.request.getStyledLayerDescriptor() );
} catch ( XMLParsingException e1 ) {
// should never happen
e1.printStackTrace();
}
AbstractLayer[] layers = sld.getLayers();
// get the number of themes assigned to the selected layers
// notice that there maybe more themes as there are layers because
// 1 .. n datasources can be assigned to one layer.
int cntTh = countNumberOfThemes( layers, this.scale );
this.themes = new Object[cntTh];
// invokes the data supplyer for each layer in an independent thread
int kk = 0;
for ( int i = 0; i < layers.length; i++ ) {
if ( layers[i] instanceof NamedLayer ) {
String styleName = null;
if ( i < this.request.getLayers().length ) {
styleName = this.request.getLayers()[i].getStyleName();
}
kk = invokeNamedLayer( layers[i], kk, styleName );
} else {
GetMapServiceInvokerForUL si =
new GetMapServiceInvokerForUL( this, (UserLayer) layers[i], kk++ );
si.start();
}
}
waitForFinished();
renderMap();
}
/**
* Invoke the named layer
*
* @param layer
* @param kk
* @param styleName
* @return int
* @throws OGCWebServiceException
*/
private int invokeNamedLayer( AbstractLayer layer, int kk, String styleName )
throws OGCWebServiceException {
Layer lay = this.configuration.getLayer( layer.getName() );
if ( validate( lay, layer.getName() ) ) {
UserStyle us = getStyles( (NamedLayer) layer, styleName );
AbstractDataSource[] ds = lay.getDataSource();
for ( int j = 0; j < ds.length; j++ ) {
ScaleHint scaleHint = ds[j].getScaleHint();
if ( this.scale >= scaleHint.getMin() && this.scale < scaleHint.getMax()
&& isValidArea( ds[j].getValidArea() ) ) {
GetMapServiceInvokerForNL si = new GetMapServiceInvokerForNL( this, lay, ds[j],
us, kk++ );
si.start();
}
}
} else {
// set theme to null if no data are available for the requested
// area and/or scale
this.themes[kk++] = null;
increaseCounter();
}
return kk;
}
/**
* returns the number of <code>DataSource</code>s involved in a GetMap request
*
* @param layers
* @param currentscale
* @return int
*/
private int countNumberOfThemes( AbstractLayer[] layers, double currentscale ) {
int cnt = 0;
for ( int i = 0; i < layers.length; i++ ) {
if ( layers[i] instanceof NamedLayer ) {
Layer lay = this.configuration.getLayer( layers[i].getName() );
AbstractDataSource[] ds = lay.getDataSource();
for ( int j = 0; j < ds.length; j++ ) {
ScaleHint scaleHint = ds[j].getScaleHint();
if ( currentscale >= scaleHint.getMin() && currentscale < scaleHint.getMax()
&& isValidArea( ds[j].getValidArea() ) ) {
cnt++;
}
}
} else {
cnt++;
}
}
return cnt;
}
/**
* returns true if the requested boundingbox intersects with the valid area of a datasource
*
* @param validArea
* @return boolean
*/
private boolean isValidArea( Geometry validArea ) {
if ( validArea != null ) {
try {
Envelope env = this.request.getBoundingBox();
Geometry geom = GeometryFactory.createSurface( env, this.reqCRS );
if ( !this.reqCRS.getName().equals( validArea.getCoordinateSystem().getName() ) ) {
// if requested CRS is not identical to the CRS of the valid area
// a transformation must be performed before intersection can
// be checked
IGeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() );
geom = gt.transform( geom );
}
return geom.intersects( validArea );
} catch ( Exception e ) {
// should never happen
LOG.logError( "could not validate WMS datasource area", e );
}
}
return true;
}
/**
* runs a loop until all sub requestes (one for each layer) has been finished or the maximum
* time limit has been exceeded.
*
* @throws OGCWebServiceException
*/
private void waitForFinished()
throws OGCWebServiceException {
if ( this.count < this.themes.length ) {
// waits until the requested layers are available as <tt>DisplayElements</tt>
// or the time limit has been reached.
// if count == themes.length then no request must be performed
long timeStamp = System.currentTimeMillis();
long lapse = 0;
long timeout = 1000 * ( this.configuration.getDeegreeParams().getRequestTimeLimit() - 1 );
do {
try {
Thread.sleep( 50 );
lapse += 50;
} catch ( InterruptedException e ) {
throw new OGCWebServiceException( "GetMapHandler", "fatal exception waiting for "
+ "GetMapHandler results" );
}
} while ( this.count < this.themes.length && lapse < timeout );
if ( System.currentTimeMillis() - timeStamp >= timeout ) {
throw new OGCWebServiceException( "Processing of the GetMap request "
+ "exceeds timelimit" );
}
}
}
/**
*
* @param layers
* @param inSLD
* @return StyledLayerDescriptor
* @throws XMLParsingException
*/
private StyledLayerDescriptor toSLD( GetMap.Layer[] layers, StyledLayerDescriptor inSLD )
throws XMLParsingException {
StyledLayerDescriptor sld = null;
if ( layers != null && layers.length > 0 && inSLD == null ) {
// Adds the content from the LAYERS and STYLES attribute to the SLD
StringBuffer sb = new StringBuffer( 5000 );
sb.append( "<?xml version=\"1.0\" encoding=\"" + CharsetUtils.getSystemCharset()
+ "\"?>" );
sb.append( "<StyledLayerDescriptor version=\"1.0.0\" " );
sb.append( "xmlns='http://www.opengis.net/sld'>" );
for ( int i = 0; i < layers.length; i++ ) {
sb.append( "<NamedLayer>" );
sb.append( "<Name>" + layers[i].getName() + "</Name>" );
sb.append( "<NamedStyle><Name>" + layers[i].getStyleName()
+ "</Name></NamedStyle></NamedLayer>" );
}
sb.append( "</StyledLayerDescriptor>" );
try {
sld = SLDFactory.createSLD( sb.toString() );
} catch ( XMLParsingException e ) {
throw new XMLParsingException( StringTools.stackTraceToString( e ) );
}
} else if ( layers != null && layers.length > 0 && inSLD != null ) {
// if layers not null and sld is not null then SLD layers just be
// considered if present in the layers list
List<String> list = new ArrayList<String>();
for ( int i = 0; i < layers.length; i++ ) {
list.add( layers[i].getName() );
}
List<AbstractLayer> newList = new ArrayList<AbstractLayer>( 20 );
AbstractLayer[] al = inSLD.getLayers();
for ( int i = 0; i < al.length; i++ ) {
if ( list.contains( al[i].getName() ) ) {
newList.add( al[i] );
}
}
al = new AbstractLayer[newList.size()];
sld = new StyledLayerDescriptor( newList.toArray( al ), inSLD.getVersion() );
} else {
// if no layers are defined ...
sld = inSLD;
}
return sld;
}
/**
* returns the <tt>UserStyle</tt>s assigned to a named layer
*
* @param sldLayer
* layer to get the styles for
* @param styleName
* requested stylename (from the KVP encoding)
* @return UserStyle
* @throws OGCWebServiceException
*/
private UserStyle getStyles( NamedLayer sldLayer, String styleName )
throws OGCWebServiceException {
AbstractStyle[] styles = sldLayer.getStyles();
UserStyle us = null;
// to avoid retrieving the layer again for each style
Layer layer = null;
layer = this.configuration.getLayer( sldLayer.getName() );
int i = 0;
while ( us == null && i < styles.length ) {
if ( styles[i] instanceof NamedStyle ) {
// styles will be taken from the WMS's style repository
us = getPredefinedStyle( styles[i].getName(), sldLayer.getName(), layer );
} else {
// if the requested style fits the name of the defined style or
// if the defined style is marked as default and the requested
// style if 'default' the condition is true. This includes that
// if more than one style with the same name or more than one
// style is marked as default always the first will be choosen
if ( styleName == null ||
( styles[i].getName() != null && styles[i].getName().equals( styleName ) ) ||
( styleName.equalsIgnoreCase( "$DEFAULT" ) && ( (UserStyle) styles[i] ).isDefault() ) ) {
us = (UserStyle) styles[i];
}
}
i++;
}
if ( us == null ) {
// this may happens if the SLD contains a named layer but not
// a style! yes this is valid according to SLD spec 1.0.0
us = getPredefinedStyle( styleName, sldLayer.getName(), layer );
}
return us;
}
/**
* Returns a Predifined UserStyle
*
* @param styleName
* @param layerName
* @param layer
* @return UserStyle
* @throws StyleNotDefinedException
*/
private UserStyle getPredefinedStyle( String styleName, String layerName, Layer layer )
throws StyleNotDefinedException {
UserStyle us = null;
if ( "default".equals( styleName ) ) {
us = layer.getStyle( styleName );
}
if ( us == null ) {
if ( styleName == null || styleName.length() == 0 || styleName.equals( "$DEFAULT" )
|| styleName.equals( "default" ) ) {
styleName = "default:" + layerName;
}
}
us = layer.getStyle( styleName );
if ( us == null && !( styleName.startsWith( "default" ) )
&& !( styleName.startsWith( "$DEFAULT" ) ) ) {
String s = Messages.getMessage( "WMS_STYLENOTDEFINED", styleName, layer );
throw new StyleNotDefinedException( s );
}
return us;
}
/**
* validates if the requested layer matches the conditions of the request if not a
* <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't
* able to deviever data for the requested area and/or scale false will be returned. If the
* layer matches the request and contains data for the requested area and/or scale true will be
* returned.
*
* @param layer
* layer as defined at the capabilities/configuration
* @param name
* name of the layer (must be submitted seperatly because the layer parameter can be
* <tt>null</tt>
* @return boolean
* @throws OGCWebServiceException
*/
private boolean validate( Layer layer, String name )
throws OGCWebServiceException {
// check if layer is available
if ( layer == null ) {
throw new LayerNotDefinedException( "Layer: " + name + " is not known by the WMS" );
}
if ( !layer.isSrsSupported( this.request.getSrs() ) ) {
throw new InvalidSRSException( "SRS: " + this.request.getSrs()
+ "is not known by layer: " + name );
}
// check for valid coordinated reference system
String[] srs = layer.getSrs();
boolean tmp = false;
for ( int i = 0; i < srs.length; i++ ) {
if ( srs[i].equalsIgnoreCase( this.request.getSrs() ) ) {
tmp = true;
break;
}
}
if ( !tmp ) {
throw new InvalidSRSException( "layer: " + name + " can't be " + "delievered in SRS: "
+ this.request.getSrs() );
}
// check bounding box
try {
Envelope bbox = this.request.getBoundingBox();
Envelope layerBbox = layer.getLatLonBoundingBox();
if ( !this.request.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) {
// transform the bounding box of the request to EPSG:4326
IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
bbox = gt.transform( bbox, this.reqCRS );
}
if ( !bbox.intersects( layerBbox ) ) {
return false;
}
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new OGCWebServiceException( "couldn't compare bounding boxes\n" + e.toString() );
}
return true;
}
/**
* put a theme to the passed index of the themes array. The second param passed is a
* <tt>Theme</tt> or an exception
*
* @param index
* @param o
*/
protected synchronized void putTheme( int index, Object o ) {
this.themes[index] = o;
}
/**
* renders the map from the <tt>DisplayElement</tt>s
*/
private void renderMap() {
// GetMapResult response = null;
OGCWebServiceException exce = null;
ArrayList<Object> list = new ArrayList<Object>( 50 );
for ( int i = 0; i < this.themes.length; i++ ) {
if ( this.themes[i] instanceof Exception ) {
exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap",
this.themes[i].toString() );
}
if ( this.themes[i] instanceof OGCWebServiceException ) {
exce = (OGCWebServiceException) this.themes[i];
break;
}
if ( this.themes[i] != null ) {
list.add( this.themes[i] );
}
}
if ( exce == null ) {
// only if no exception occured
try {
Theme[] th = list.toArray( new Theme[list.size()] );
MapView map = null;
if ( th.length > 0 ) {
map = MapFactory.createMapView( "deegree WMS", this.request.getBoundingBox(),
this.reqCRS, th );
}
this.graph.setClip( 0, 0, this.request.getWidth(), this.request.getHeight() );
if ( !this.request.getTransparency() ) {
this.graph.setColor( this.request.getBGColor() );
this.graph.fillRect( 0, 0, this.request.getWidth(), this.request.getHeight() );
}
if ( map != null ) {
Theme[] allthemes = map.getAllThemes();
map.addOptimizer( new LabelOptimizer( allthemes ) );
// antialiasing must be switched of for gif output format
// because the antialiasing may create more than 255 colors
// in the map/image, even just a few colors are defined in
// the styles
if ( !this.configuration.getDeegreeParams().isAntiAliased() ) {
( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON );
( (Graphics2D) this.graph ).setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING,
RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
}
map.paint( this.graph );
}
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
exce = new OGCWebServiceException( "GetMapHandler_Impl: renderMap", e.toString() );
}
}
// print a copyright note at the left lower corner of the map
printCopyright( this.graph, this.request.getHeight() );
}
/**
* prints a copyright note at left side of the map bottom. The copyright note will be extracted
* from the WMS capabilities/configuration
*
* @param g
* graphic context of the map
* @param heigth
* height of the map in pixel
*/
private void printCopyright( Graphics g, int heigth ) {
WMPSDeegreeParams dp = this.configuration.getDeegreeParams();
String copyright = dp.getCopyright();
if ( this.copyrightImg != null ) {
g.drawImage( this.copyrightImg, 8, heigth - this.copyrightImg.getHeight() - 5, null );
} else {
if ( copyright != null ) {
g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
g.setColor( Color.BLACK );
g.drawString( copyright, 8, heigth - 15 );
g.drawString( copyright, 10, heigth - 15 );
g.drawString( copyright, 8, heigth - 13 );
g.drawString( copyright, 10, heigth - 13 );
g.setColor( Color.WHITE );
g.setFont( new Font( "SANSSERIF", Font.PLAIN, 14 ) );
g.drawString( copyright, 9, heigth - 14 );
// g.dispose();
}
}
}
}
/* *************************************************************************************************
Changes to this class. What the people have been up to:
$Log: DefaultGetMapHandler.java,v $
Revision 1.35 2006/11/29 13:00:54 schmitz
Cleaned up WMS messages.
Revision 1.34 2006/11/27 09:07:52 poth
JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code.
Revision 1.32 2006/10/17 20:31:19 poth
*** empty log message ***
Revision 1.31 2006/10/02 06:30:35 poth
bug fixes
Revision 1.30 2006/09/27 16:46:41 poth
transformation method signature changed
Revision 1.29 2006/09/25 20:31:37 poth
bug fixes - map scale calculation
Revision 1.28 2006/09/25 12:47:00 poth
bug fixes - map scale calculation
Revision 1.27 2006/09/22 12:45:46 mays
formatting
Revision 1.26 2006/09/04 11:32:25 deshmukh
comments added
Revision 1.25 2006/08/10 07:11:35 deshmukh
WMPS has been modified to support the new configuration changes
and the excess code not needed has been replaced.
Revision 1.24 2006/08/01 13:41:47 deshmukh
The wmps configuration has been modified and extended. Also fixed the javadoc.
Revision 1.23 2006/07/31 11:21:06 deshmukh
wmps implemention...
Revision 1.22 2006/07/12 14:46:16 poth
comment footer added
************************************************************************************************ */