//$HeadURL$
/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2008 by:
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
53177 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.igeo.dataadapter.wms;
import static org.deegree.framework.util.MapUtils.DEFAULT_PIXEL_SIZE;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.framework.util.HttpUtils;
import org.deegree.framework.util.ImageUtils;
import org.deegree.framework.util.KVP2Map;
import org.deegree.framework.util.MapUtils;
import org.deegree.framework.util.StringTools;
import org.deegree.framework.xml.XMLFragment;
import org.deegree.framework.xml.XMLParsingException;
import org.deegree.framework.xml.XMLTools;
import org.deegree.igeo.ApplicationContainer;
import org.deegree.igeo.dataadapter.DataAccessException;
import org.deegree.igeo.dataadapter.GridCoverageAdapter;
import org.deegree.igeo.dataadapter.OWSURLUtils;
import org.deegree.igeo.i18n.Messages;
import org.deegree.igeo.mapmodel.Datasource;
import org.deegree.igeo.mapmodel.Layer;
import org.deegree.igeo.mapmodel.MapModel;
import org.deegree.igeo.mapmodel.WMSDatasource;
import org.deegree.igeo.settings.WMSGridCoverageAdapterSettings;
import org.deegree.model.Identifier;
import org.deegree.model.coverage.grid.GridCoverage;
import org.deegree.model.coverage.grid.ImageGridCoverage;
import org.deegree.model.crs.CRSTransformationException;
import org.deegree.model.crs.GeoTransformer;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.model.feature.GMLFeatureCollectionDocument;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.ogcwebservices.OGCWebServiceException;
import org.deegree.ogcwebservices.OWSUtils;
import org.deegree.ogcwebservices.getcapabilities.InvalidCapabilitiesException;
import org.deegree.ogcwebservices.wcs.describecoverage.CoverageDescription;
import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilities;
import org.deegree.ogcwebservices.wms.capabilities.WMSCapabilitiesDocument;
import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
import org.deegree.ogcwebservices.wms.operation.GetMap;
import org.xml.sax.SAXException;
/**
* Concrete implementation of {@link GridCoverageAdapter} for reading raster data from a WMS
*
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
* @author last edited by: $Author$
*
* @version. $Revision$, $Date$
*/
public class WMSGridCoverageAdapter extends GridCoverageAdapter {
private static final ILogger LOG = LoggerFactory.getLogger( WMSGridCoverageAdapter.class );
private GetMap baseRequest;
private int timeout = 25000;
private URL getMapHttpGetURL;
@SuppressWarnings("unused")
private URL getMapHttpPostURL;
private URL getFeatureInfoURL;
private String version;
private WMSGridCoverageAdapterSettings wmsSet;
private static Map<String, XMLFragment> capabilitiesCache;
private URL baseURL;
static {
if ( capabilitiesCache == null ) {
capabilitiesCache = new HashMap<String, XMLFragment>();
}
}
/**
*
* @param datasource
* @param layer
* @param mapModel
* @param baseURL
*/
public WMSGridCoverageAdapter( Datasource datasource, Layer layer, MapModel mapModel, URL baseURL ) {
super( datasource, layer, mapModel );
this.baseURL = baseURL;
String tmp = ( (WMSDatasource) datasource ).getBaseRequest();
Envelope bbox = mapModel.getEnvelope();
datasource.setExtent( bbox );
tmp = StringTools.concat( 2000, tmp, "&BBOX=", bbox.getMin().getX(), ',', bbox.getMin().getY(), ',',
bbox.getMax().getX(), ',', bbox.getMax().getY() );
wmsSet = mapModel.getApplicationContainer().getSettings().getWMSGridCoveragesAdapter();
// read request timeout from deegree configuration
timeout = wmsSet.getTimeout();
// try using baseURL; if using baseURL fails when performing a request, URLs will be read
// from capabilities
useBaseURL( baseURL );
Map<String, String> param = KVP2Map.toMap( tmp );
// ensure that GetMap request will be created for version read from WMS capabilities
param.put( "ID", new Identifier().getAsQualifiedString() );
version = ( (WMSDatasource) datasource ).getServiceVersion();
LOG.logDebug( "requested WMS version: ", version );
param.put( "VERSION", version );
// dummy values that will be overwritten when requsting a map
param.put( "WIDTH", "10" );
param.put( "HEIGHT", "10" );
if ( "1.3.0".compareTo( version ) <= 0 ) {
param.put( "CRS", bbox.getCoordinateSystem().getPrefixedName() );
} else {
param.put( "SRS", bbox.getCoordinateSystem().getPrefixedName() );
}
try {
baseRequest = GetMap.create( param );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10029", tmp ) );
}
}
/**
*
* @param baseURL
*/
private void useBaseURL( URL baseURL ) {
try {
URL url = OWSURLUtils.normalizeOWSURL( baseURL );
getMapHttpGetURL = url;
getMapHttpPostURL = url;
getFeatureInfoURL = url;
} catch ( MalformedURLException e ) {
LOG.logError( e );
}
}
/**
* reads destination URLs for GetFeature, DescribeFeatureType and Transaction requests from WFS capabilities
* document
*
* @param baseURL
*/
private void readURLs( URL baseURL ) {
LOG.logInfo( "read request URLs from capabilities" );
XMLFragment xml;
String capabilitiesUrl = null;
try {
ApplicationContainer<?> appCont = mapModel.getApplicationContainer();
capabilitiesUrl = baseURL.toURI().toASCIIString();
String tmp = HttpUtils.normalizeURL( capabilitiesUrl );
capabilitiesUrl = HttpUtils.addAuthenticationForKVP( capabilitiesUrl, appCont.getUser(),
appCont.getPassword(), appCont.getCertificate( tmp ) );
baseURL = new URL( capabilitiesUrl );
if ( capabilitiesCache.containsKey( capabilitiesUrl ) ) {
xml = capabilitiesCache.get( capabilitiesUrl );
LOG.logInfo( "read capabilities from cache: ", capabilitiesUrl );
} else {
InputStream is = HttpUtils.performHttpGet( capabilitiesUrl, null, wmsSet.getTimeout(),
appCont.getUser(), appCont.getPassword(), null ).getResponseBodyAsStream();
xml = new XMLFragment();
xml.load( is, baseURL.toExternalForm() );
capabilitiesCache.put( capabilitiesUrl, xml );
}
} catch ( SAXException e ) {
LOG.logError( e.getMessage(), e );
String s = StringTools.stackTraceToString( e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10033", capabilitiesUrl ) + s );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
String s = StringTools.stackTraceToString( e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10032", capabilitiesUrl ) + s );
}
try {
version = XMLTools.getRequiredAttrValue( "version", null, xml.getRootElement() );
} catch ( XMLParsingException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10034", capabilitiesUrl,
xml.getAsPrettyString() ) );
}
WMSCapabilitiesDocument doc = new WMSCapabilitiesDocument();
doc.setRootElement( xml.getRootElement() );
WMSCapabilities wmsCapa;
try {
wmsCapa = (WMSCapabilities) doc.parseCapabilities();
} catch ( InvalidCapabilitiesException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10034", capabilitiesUrl,
xml.getAsPrettyString() ) );
}
getMapHttpGetURL = OWSUtils.getHTTPGetOperationURL( wmsCapa, GetMap.class );
getMapHttpPostURL = OWSUtils.getHTTPPostOperationURL( wmsCapa, GetMap.class );
getFeatureInfoURL = OWSUtils.getHTTPGetOperationURL( wmsCapa, GetFeatureInfo.class );
}
/**
*
* @param coverageDescription
*/
public void setCoverageDescription( CoverageDescription coverageDescription ) {
throw new UnsupportedOperationException();
}
/**
*
* @param gridCoverage
*/
public void setCoverage( GridCoverage gridCoverage ) {
throw new UnsupportedOperationException();
}
/**
*
* @return adapted coverage
*/
public GridCoverage getCoverage() {
readMapImage();
return this.coverage;
}
/**
* reads a map image from a WMS and assigns it to the internal grid coverage
*
*/
private void readMapImage() {
fireStartLoadingEvent();
// update GetMap request with current state of the map model
Envelope bbox = mapModel.getEnvelope();
int width = mapModel.getTargetDevice().getPixelWidth();
int height = mapModel.getTargetDevice().getPixelHeight();
String srs = mapModel.getCoordinateSystem().getPrefixedName();
GeoTransformer transformBack = null;
if ( !datasource.getNativeCoordinateSystem().equals( mapModel.getCoordinateSystem() ) ) {
// perform CRS transformation of the request bounding box and resizing of the requested image
// to enable transformation and cutting the result of a GetMap request back to the CRS of
// the map model without lost of data at the image boundaries
srs = datasource.getNativeCoordinateSystem().getPrefixedName();
GeoTransformer transformer = new GeoTransformer( datasource.getNativeCoordinateSystem() );
transformBack = new GeoTransformer( mapModel.getCoordinateSystem() );
try {
bbox = transformer.transform( bbox, mapModel.getCoordinateSystem(), true );
} catch ( CRSTransformationException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( e.getMessage() );
}
double scale = MapUtils.calcScale( width, height, mapModel.getEnvelope(), mapModel.getCoordinateSystem(),
DEFAULT_PIXEL_SIZE );
double newScale = MapUtils.calcScale( width, height, bbox, datasource.getNativeCoordinateSystem(),
DEFAULT_PIXEL_SIZE );
double ratio = scale / newScale;
width = (int) Math.round( width * ratio );
height = (int) Math.round( height * ratio );
}
baseRequest.setSrs( srs );
baseRequest.setBoundingBox( bbox );
baseRequest.setWidth( width );
baseRequest.setHeight( height );
InputStream is = null;
String getMapUrl = null;
try {
ApplicationContainer<?> appCont = mapModel.getApplicationContainer();
getMapUrl = getMapHttpGetURL.toURI().toASCIIString();
boolean allowSwapAxis = ( (WMSDatasource) datasource ).isAllowSwapAxis();
String param = HttpUtils.addAuthenticationForKVP( baseRequest.getRequestParameter( allowSwapAxis ),
appCont.getUser(), appCont.getPassword(),
appCont.getCertificate( getMapUrl ) );
LOG.logDebug( "base WMS URL ", getMapUrl );
LOG.logDebug( "WMS GetMap parameter ", param );
is = HttpUtils.performHttpGet( getMapUrl, param, timeout, appCont.getUser(), appCont.getPassword(), null ).getResponseBodyAsStream();
} catch ( Exception e ) {
if ( getMapHttpGetURL == null ) {
// read target URLs (and WMS version) from WMS capabilities
readURLs( baseURL );
readMapImage();
return;
}
LOG.logError( e.getMessage(), e );
fireLoadingExceptionEvent();
try {
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10030", getMapUrl,
baseRequest.getRequestParameter() ) );
} catch ( OGCWebServiceException e1 ) {
e1.printStackTrace();
}
}
// read data from WMS into a byte array to enable debugging and detailed error messages
// if result can not be parsed as image
ByteArrayOutputStream bos = new ByteArrayOutputStream( 100000 );
byte[] b = new byte[100000];
try {
int c = is.read( b );
do {
bos.write( b, 0, c );
c = is.read( b );
} while ( c > 0 );
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
try {
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10031", getMapUrl,
baseRequest.getRequestParameter(), e.getMessage() ) );
} catch ( OGCWebServiceException e1 ) {
e1.printStackTrace();
}
}
BufferedImage image = null;
try {
image = ImageUtils.loadImage( new ByteArrayInputStream( bos.toByteArray() ) );
bos.close();
} catch ( Exception e ) {
LOG.logError( e.getMessage(), e );
try {
throw new DataAccessException( Messages.getMessage( Locale.getDefault(), "$DG10031", getMapUrl,
baseRequest.getRequestParameter(),
new String( bos.toByteArray() ) ) );
} catch ( OGCWebServiceException e1 ) {
e1.printStackTrace();
}
}
if ( !datasource.getNativeCoordinateSystem().equals( mapModel.getCoordinateSystem() ) ) {
// perform transformation and cutting of the result of a GetMap request if map model CRS and
// CRS supported by requested layer(s) are not the same
try {
image = transformBack.transform( image, bbox, mapModel.getEnvelope(),
mapModel.getTargetDevice().getPixelWidth(),
mapModel.getTargetDevice().getPixelHeight(), 25, 3, null );
} catch ( CRSTransformationException e ) {
LOG.logError( e.getMessage(), e );
throw new DataAccessException( e.getMessage() );
}
}
this.coverage = new ImageGridCoverage( null, mapModel.getEnvelope(), image );
fireLoadingFinishedEvent();
}
/**
*
* @param envelope
* @return result to feature info
*/
public FeatureCollection getFeatureInfo( int x, int y )
throws Exception {
try {
Envelope bbox = mapModel.getEnvelope();
int width = mapModel.getTargetDevice().getPixelWidth();
int height = mapModel.getTargetDevice().getPixelHeight();
String srs = mapModel.getCoordinateSystem().getPrefixedName();
baseRequest.setSrs( srs );
baseRequest.setBoundingBox( bbox );
baseRequest.setWidth( width );
baseRequest.setHeight( height );
ApplicationContainer<?> appCont = mapModel.getApplicationContainer();
String getFiUrl = getFeatureInfoURL.toURI().toASCIIString();
StringBuilder sb = new StringBuilder( 1000 );
int cnt = appCont.getSettings().getWMSGridCoveragesAdapter().getFeatureCount();
sb.append( "feature_count=" ).append( cnt ).append( "&" );
if ( baseRequest.getVersion().equals( "1.3.0" ) ) {
sb.append( "i=" ).append( x ).append( "&" );
sb.append( "j=" ).append( y ).append( "&" );
} else {
sb.append( "x=" ).append( x ).append( "&" );
sb.append( "y=" ).append( y ).append( "&" );
}
sb.append( StringTools.replace( baseRequest.getRequestParameter(), "GetMap", "GetFeatureInfo", false ) );
sb.append( "&QUERY_layers=" );
org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] ll = baseRequest.getLayers();
for ( int i = 0; i < ll.length; i++ ) {
sb.append( ll[i].getName() );
if ( i < ll.length - 1 ) {
sb.append( ',' );
}
}
String param = HttpUtils.addAuthenticationForKVP( sb.toString(), appCont.getUser(), appCont.getPassword(),
appCont.getCertificate( getFiUrl ) );
LOG.logDebug( "GetFeatureInfo request: ", param );
LOG.logDebug( "GetFeatureInfo request URL: ", getFiUrl );
InputStream is = HttpUtils.performHttpGet( getFiUrl, param, timeout, appCont.getUser(),
appCont.getPassword(), null ).getResponseBodyAsStream();
GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument( true );
doc.load( is, getFiUrl );
if ( doc.getRootElement().getLocalName().equalsIgnoreCase( "ServiceExceptionReport" ) ) {
throw new Exception( doc.getAsPrettyString() );
}
return doc.parse();
} catch ( Exception e ) {
if ( getFeatureInfoURL == null ) {
// read target URLs (and WMS version) from WMS capabilities
readURLs( baseURL );
return getFeatureInfo( x, y );
}
throw e;
}
}
@Override
public void refresh() {
// TODO Auto-generated method stub
}
@Override
public void refresh( boolean forceReload ) {
// TODO Auto-generated method stub
}
@Override
public void commitChanges()
throws IOException {
// A WMSGridCoverageAdpter can not deal changes and so can/need not to commit changes to its
// backend/WMS
}
}