001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/DefaultGetFeatureInfoHandler.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.lang.System.currentTimeMillis; 039 import static org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory.createGetFeatureInfoResponse; 040 041 import java.awt.Color; 042 import java.awt.image.BufferedImage; 043 import java.io.BufferedReader; 044 import java.io.ByteArrayInputStream; 045 import java.io.IOException; 046 import java.io.InputStream; 047 import java.io.InputStreamReader; 048 import java.io.Reader; 049 import java.io.StringReader; 050 import java.net.URI; 051 import java.net.URL; 052 import java.util.ArrayList; 053 import java.util.Arrays; 054 import java.util.Iterator; 055 import java.util.LinkedList; 056 import java.util.List; 057 import java.util.Map; 058 import java.util.concurrent.Callable; 059 import java.util.concurrent.CancellationException; 060 061 import org.deegree.datatypes.QualifiedName; 062 import org.deegree.datatypes.Types; 063 import org.deegree.datatypes.UnknownTypeException; 064 import org.deegree.datatypes.values.Values; 065 import org.deegree.framework.concurrent.DoDatabaseQueryTask; 066 import org.deegree.framework.concurrent.DoExternalAccessTask; 067 import org.deegree.framework.concurrent.DoServiceTask; 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.CharsetUtils; 073 import org.deegree.framework.util.IDGenerator; 074 import org.deegree.framework.util.MapUtils; 075 import org.deegree.framework.xml.NamespaceContext; 076 import org.deegree.framework.xml.XMLFragment; 077 import org.deegree.framework.xml.XMLTools; 078 import org.deegree.framework.xml.XSLTDocument; 079 import org.deegree.graphics.transformation.GeoTransform; 080 import org.deegree.graphics.transformation.WorldToScreenTransform; 081 import org.deegree.i18n.Messages; 082 import org.deegree.model.coverage.grid.ImageGridCoverage; 083 import org.deegree.model.crs.CRSFactory; 084 import org.deegree.model.crs.CoordinateSystem; 085 import org.deegree.model.crs.GeoTransformer; 086 import org.deegree.model.feature.Feature; 087 import org.deegree.model.feature.FeatureCollection; 088 import org.deegree.model.feature.FeatureFactory; 089 import org.deegree.model.feature.FeatureProperty; 090 import org.deegree.model.feature.GMLFeatureCollectionDocument; 091 import org.deegree.model.feature.schema.FeatureType; 092 import org.deegree.model.feature.schema.PropertyType; 093 import org.deegree.model.filterencoding.ComplexFilter; 094 import org.deegree.model.filterencoding.FeatureFilter; 095 import org.deegree.model.filterencoding.FeatureId; 096 import org.deegree.model.filterencoding.Filter; 097 import org.deegree.model.spatialschema.Envelope; 098 import org.deegree.model.spatialschema.GMLGeometryAdapter; 099 import org.deegree.model.spatialschema.Geometry; 100 import org.deegree.model.spatialschema.GeometryFactory; 101 import org.deegree.ogcbase.CommonNamespaces; 102 import org.deegree.ogcbase.InvalidSRSException; 103 import org.deegree.ogcbase.PropertyPath; 104 import org.deegree.ogcwebservices.OGCWebServiceException; 105 import org.deegree.ogcwebservices.OGCWebServiceRequest; 106 import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage; 107 import org.deegree.ogcwebservices.wfs.RemoteWFService; 108 import org.deegree.ogcwebservices.wfs.WFService; 109 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 110 import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType; 111 import org.deegree.ogcwebservices.wfs.operation.FeatureResult; 112 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 113 import org.deegree.ogcwebservices.wfs.operation.Query; 114 import org.deegree.ogcwebservices.wms.capabilities.Dimension; 115 import org.deegree.ogcwebservices.wms.capabilities.Layer; 116 import org.deegree.ogcwebservices.wms.capabilities.ScaleHint; 117 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 118 import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource; 119 import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource; 120 import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource; 121 import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource; 122 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType; 123 import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess; 124 import org.deegree.ogcwebservices.wms.operation.DimensionValues; 125 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo; 126 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult; 127 import org.deegree.ogcwebservices.wms.operation.GetMap; 128 import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory; 129 import org.deegree.processing.raster.converter.Image2RawData; 130 import org.w3c.dom.Document; 131 132 /** 133 * 134 * 135 * 136 * @version $Revision: 24806 $ 137 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 138 * @author last edited by: $Author: apoth $ 139 * 140 * @version 1.0. $Revision: 24806 $, $Date: 2010-06-09 16:41:16 +0200 (Mi, 09 Jun 2010) $ 141 * 142 */ 143 class DefaultGetFeatureInfoHandler implements GetFeatureInfoHandler { 144 145 protected static final ILogger LOG = LoggerFactory.getLogger( DefaultGetFeatureInfoHandler.class ); 146 147 private static final double DEFAULT_PIXEL_SIZE = 0.00028; 148 149 protected GetFeatureInfo request = null; 150 151 protected GetMap getMapRequest = null; 152 153 protected WMSConfigurationType configuration = null; 154 155 // collects the reponse for each layer 156 private Object[] featCol = null; 157 158 // scale of the map 159 protected double scale = 0; 160 161 // CRS of the request 162 protected CoordinateSystem reqCRS = null; 163 164 protected static final QualifiedName VALUE = new QualifiedName( "value" ); 165 166 /** 167 * Creates a new GetMapHandler object. 168 * 169 * @param capabilities 170 * @param request 171 * request to perform 172 * @throws OGCWebServiceException 173 */ 174 public DefaultGetFeatureInfoHandler( WMSConfigurationType capabilities, GetFeatureInfo request ) 175 throws OGCWebServiceException { 176 177 this.request = request; 178 this.configuration = capabilities; 179 getMapRequest = request.getGetMapRequestCopy(); 180 try { 181 reqCRS = CRSFactory.create( getMapRequest.getSrs() ); 182 if ( reqCRS == null ) { 183 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) ); 184 } 185 } catch ( Exception e ) { 186 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) ); 187 } 188 try { 189 Envelope bbox = getMapRequest.getBoundingBox(); 190 double pixelSize = 1; 191 if ( request.getVersion().equals( "1.3.0" ) ) { 192 // required because for WMS 1.3.0 'scale' represents the ScaleDenominator 193 // and for WMS < 1.3.0 it represents the size of a pixel diagonal in meter 194 pixelSize = DEFAULT_PIXEL_SIZE; 195 } 196 scale = MapUtils.calcScale( getMapRequest.getWidth(), getMapRequest.getHeight(), bbox, reqCRS, pixelSize ); 197 } catch ( Exception e ) { 198 LOG.logDebug( e.getLocalizedMessage(), e ); 199 throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC", e ) ); 200 } 201 202 } 203 204 /** 205 * performs a GetFeatureInfo request and retruns the result encapsulated within a <tt>WMSFeatureInfoResponse</tt> 206 * object. 207 * <p> 208 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs that makes it 209 * imposible to return a result. If something wents wrong performing the request (none fatal error) The exception 210 * shall be encapsulated within the response object to be returned to the client as requested 211 * (GetFeatureInfo-Request EXCEPTION-Parameter). 212 * 213 * <p> 214 * All sublayers of the queried layer will be added automatically. Non-queryable sublayers are then ignored in the 215 * response. 216 * </p> 217 * 218 * @return response to the GetFeatureInfo response 219 */ 220 public GetFeatureInfoResult performGetFeatureInfo() 221 throws OGCWebServiceException { 222 long runningTime = currentTimeMillis(); 223 224 String[] qlayers = request.getQueryLayers(); 225 226 List<Layer> allLayers = new ArrayList<Layer>(); 227 228 // here, the explicitly queried layers are checked for being queryable and known 229 for ( int i = 0; i < qlayers.length; i++ ) { 230 Layer layer = configuration.getLayer( qlayers[i] ); 231 232 if ( layer == null ) { 233 throw new LayerNotDefinedException( Messages.getMessage( "WMS_UNKNOWNLAYER", qlayers[i] ) ); 234 } 235 if ( !layer.isQueryable() ) { 236 throw new LayerNotQueryableException( Messages.getMessage( "WMS_LAYER_NOT_QUERYABLE", qlayers[i] ) ); 237 } 238 if ( !layer.isSrsSupported( getMapRequest.getSrs() ) ) { 239 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS_FOR_LAYER", 240 getMapRequest.getSrs(), qlayers[i] ) ); 241 } 242 243 allLayers.add( layer ); 244 245 // sub layers are added WITHOUT being checked for being queryable 246 // This is desirable for example in the following scenario: 247 // Suppose one queryable layer contains a lot of other layers, 248 // that are mostly queryable. Then you can query all of those layers 249 // at once by just querying the enclosing layer (the unqueryable 250 // sub layers are ignored). 251 allLayers.addAll( Arrays.asList( layer.getLayer() ) ); 252 } 253 254 Layer[] layerList = allLayers.toArray( new Layer[allLayers.size()] ); 255 256 if ( layerList.length == 1 && layerList[0].getDataSource().length == 1 257 && layerList[0].getDataSource()[0].getFeatureInfoURL() != null ) { 258 LOG.logDebug( "GetFeatureInfo predefined response workaround enabled." ); 259 try { 260 InputStream stream = layerList[0].getDataSource()[0].getFeatureInfoURL().openStream(); 261 BufferedReader in = new BufferedReader( new InputStreamReader( stream ) ); 262 StringBuilder sb = new StringBuilder(); 263 264 while ( in.ready() ) { 265 sb.append( in.readLine() ).append( "\n" ); 266 } 267 268 in.close(); 269 270 return createGetFeatureInfoResponse( request, null, sb.toString() ); 271 } catch ( IOException e ) { 272 LOG.logError( "Predefined GetFeatureInfo response could not be read/found.", e ); 273 } 274 } 275 276 LinkedList<Callable<Object>> tasks = new LinkedList<Callable<Object>>(); 277 for ( int i = 0; i < layerList.length; i++ ) { 278 if ( validate( layerList[i] ) ) { 279 AbstractDataSource datasource[] = layerList[i].getDataSource(); 280 for ( int j = 0; j < datasource.length; j++ ) { 281 ScaleHint hint = datasource[j].getScaleHint(); 282 if ( datasource[j].isQueryable() && isValidArea( datasource[j].getValidArea() ) 283 && scale >= hint.getMin() && scale < hint.getMax() ) { 284 ServiceInvoker si = new ServiceInvoker( layerList[i], datasource[j] ); 285 tasks.add( si ); 286 } else { 287 LOG.logDebug( "Not using a datasource of layer " + layerList[i].getName() 288 + " due to scale or bbox limits." ); 289 } 290 } 291 } else { 292 LOG.logDebug( "Layer " + layerList[i].getName() + " not validated." ); 293 } 294 } 295 296 // was: subtract 1 second for architecture overhead and image creation 297 // am not going to do this! 298 long timeLimit = 1000 * ( configuration.getDeegreeParams().getRequestTimeLimit() ); 299 timeLimit -= ( runningTime - currentTimeMillis() ); 300 try { 301 List<ExecutionFinishedEvent<Object>> list = Executor.getInstance().performSynchronously( tasks, timeLimit ); 302 featCol = new Object[list.size()]; 303 int i = 0; 304 for ( ExecutionFinishedEvent<Object> evt : list ) { 305 try { 306 Object o = evt.getResult(); 307 featCol[i++] = o; 308 } catch ( CancellationException e ) { 309 createExceptionResponse( e ); 310 } catch ( Exception e ) { 311 createExceptionResponse( e ); 312 } catch ( Throwable e ) { 313 LOG.logError( "Unknown error", e ); 314 } 315 } 316 } catch ( InterruptedException e ) { 317 createExceptionResponse( e ); 318 } 319 320 GetFeatureInfoResult res = createFeatureInfoResponse(); 321 322 return res; 323 } 324 325 /** 326 * returns true if the requested boundingbox intersects with the valid area of a datasource 327 * 328 * @param validArea 329 */ 330 private boolean isValidArea( Geometry validArea ) { 331 332 if ( validArea != null ) { 333 try { 334 Envelope env = request.getGetMapRequestCopy().getBoundingBox(); 335 Geometry geom = GeometryFactory.createSurface( env, reqCRS ); 336 if ( !reqCRS.getIdentifier().equals( validArea.getCoordinateSystem().getIdentifier() ) ) { 337 // if requested CRS is not identical to the CRS of the valid area 338 // a transformation must be performed before intersection can 339 // be checked 340 GeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() ); 341 geom = gt.transform( geom ); 342 } 343 return geom.intersects( validArea ); 344 } catch ( Exception e ) { 345 // should never happen 346 LOG.logError( "Could not validate WMS datasource area", e ); 347 } 348 } 349 return true; 350 } 351 352 /** 353 * validates if the requested layer matches the conditions of the request if not a <tt>WebServiceException</tt> will 354 * be thrown. If the layer matches the request, but isn't able to deliver data for the requested area and/or scale 355 * false will be returned. If the layer matches the request and contains data for the requested area and/or scale 356 * true will be returned. 357 * 358 * @param layer 359 * layer as defined at the capabilities/configuration 360 */ 361 private boolean validate( Layer layer ) 362 throws OGCWebServiceException { 363 364 if ( layer == null ) { 365 return false; 366 } 367 368 // why the layer can be null here is not known, but just in case: 369 String name = layer.getName(); 370 371 // check for valid coordinated reference system 372 String[] srs = layer.getSrs(); 373 boolean tmp = false; 374 for ( int i = 0; i < srs.length; i++ ) { 375 if ( srs[i].equalsIgnoreCase( getMapRequest.getSrs() ) ) { 376 tmp = true; 377 break; 378 } 379 } 380 381 if ( !tmp ) { 382 throw new InvalidSRSException( Messages.getMessage( "WMS_INVALIDSRS", name, getMapRequest.getSrs() ) ); 383 } 384 385 // check bounding box 386 try { 387 388 Envelope bbox = getMapRequest.getBoundingBox(); 389 Envelope layerBbox = layer.getLatLonBoundingBox(); 390 if ( !getMapRequest.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) { 391 // transform the bounding box of the request to EPSG:4326 392 GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) ); 393 bbox = gt.transform( bbox, reqCRS ); 394 } 395 396 if ( !bbox.intersects( layerBbox ) ) { 397 return false; 398 } 399 400 } catch ( Exception e ) { 401 throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) ); 402 } 403 404 return true; 405 } 406 407 /** 408 * creates a <tt>GetFeatureInfoResult</tt> containing an <tt>OGCWebServiceException</tt> 409 * 410 * @param e 411 * exception to encapsulate into the response 412 */ 413 private GetFeatureInfoResult createExceptionResponse( Exception e ) { 414 415 OGCWebServiceException exce = null; 416 417 // default --> application/vnd.ogc.se_xml 418 exce = new OGCWebServiceException( getClass().getName(), e.getMessage() ); 419 420 GetFeatureInfoResult res = WMSProtocolFactory.createGetFeatureInfoResponse( request, exce, null ); 421 422 return res; 423 } 424 425 /** 426 * generates the desired output from the GMLs 427 * 428 * @return the result object 429 * @throws OGCWebServiceException 430 */ 431 private GetFeatureInfoResult createFeatureInfoResponse() 432 throws OGCWebServiceException { 433 434 Envelope bbox = getMapRequest.getBoundingBox(); 435 436 StringBuffer sb = new StringBuffer( 20000 ); 437 sb.append( "<ll:FeatureCollection " ); 438 439 URL schemaLoc = configuration.getDeegreeParams().getFeatureSchemaLocation(); 440 if ( schemaLoc != null ) { 441 sb.append( "xsi:schemaLocation='" ); 442 sb.append( configuration.getDeegreeParams().getFeatureSchemaNamespace() ); 443 sb.append( " " ); 444 sb.append( schemaLoc.toExternalForm() ); 445 sb.append( "'" ); 446 } 447 448 sb.append( " xmlns:gml='http://www.opengis.net/gml' " ); 449 sb.append( "xmlns:ll='http://www.lat-lon.de' " ); 450 sb.append( "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " ); 451 URL url = configuration.getDeegreeParams().getSchemaLocation(); 452 if ( url != null ) { 453 sb.append( "xsi:schemaLocation='" ); 454 sb.append( "http://www.lat-lon.de " + url.toExternalForm() + "'" ); 455 } 456 sb.append( "><gml:boundedBy>" ); 457 sb.append( "<gml:Box srsName='" + getMapRequest.getSrs() + "'>" ); 458 sb.append( "<gml:coordinates>" + bbox.getMin().getX() + "," ); 459 sb.append( bbox.getMin().getY() + " " + bbox.getMax().getX() + "," ); 460 sb.append( bbox.getMax().getY() + "</gml:coordinates>" ); 461 sb.append( "</gml:Box></gml:boundedBy>" ); 462 463 int cnt = 0; 464 465 for ( int i = 0; i < featCol.length; i++ ) { 466 if ( featCol[i] instanceof OGCWebServiceException ) { 467 throw (OGCWebServiceException) featCol[i]; 468 } 469 if ( featCol[i] != null ) { 470 FeatureCollection fc = (FeatureCollection) featCol[i]; 471 cnt = appendFeatureCollection( fc, sb, cnt ); 472 } 473 474 // if ( cnt >= request.getFeatureCount() ) break; 475 } 476 sb.append( "</ll:FeatureCollection>" ); 477 LOG.logDebug( "original feature info response: ", sb ); 478 GetFeatureInfoResult response = WMSProtocolFactory.createGetFeatureInfoResponse( request, null, sb.toString() ); 479 480 return response; 481 } 482 483 /** 484 * 485 * @param col 486 * @param sb 487 * @param cnt 488 * @return a counter, probably the same that is given as argument 489 */ 490 private int appendFeatureCollection( FeatureCollection col, StringBuffer sb, int cnt ) { 491 492 Feature[] feat = col.toArray(); 493 if ( feat != null ) { 494 for ( int j = 0; j < feat.length; j++ ) { 495 FeatureType ft = feat[j].getFeatureType(); 496 PropertyType[] ftp = ft.getProperties(); 497 cnt++; 498 sb.append( "<gml:featureMember>" ); 499 sb.append( "<ll:" ).append( ft.getName().getLocalName() ); 500 sb.append( " fid='" ).append( feat[j].getId().replace( ' ', '_' ) ).append( "'>" ); 501 for ( int i = 0; i < ftp.length; i++ ) { 502 if ( ftp[i].getType() != Types.GEOMETRY && ftp[i].getType() != Types.POINT 503 && ftp[i].getType() != Types.CURVE && ftp[i].getType() != Types.SURFACE 504 && ftp[i].getType() != Types.MULTIPOINT && ftp[i].getType() != Types.MULTICURVE 505 && ftp[i].getType() != Types.MULTISURFACE ) { 506 507 FeatureProperty[] props = feat[j].getProperties( ftp[i].getName() ); 508 if ( props != null ) { 509 for ( FeatureProperty property : props ) { 510 Object value = property.getValue(); 511 sb.append( "<ll:" + ftp[i].getName().getLocalName() + ">" ); 512 if ( value instanceof FeatureCollection ) { 513 FeatureCollection fc = (FeatureCollection) value; 514 appendFeatureCollection( fc, sb, cnt ); 515 } else { 516 sb.append( "<![CDATA[" ).append( value ).append( "]]>" ); 517 } 518 sb.append( "</ll:" + ftp[i].getName().getLocalName() + ">" ); 519 } 520 } 521 } 522 } 523 sb.append( "</ll:" ).append( ft.getName().getLocalName() ).append( '>' ); 524 sb.append( "</gml:featureMember>" ); 525 if ( cnt >= request.getFeatureCount() ) 526 break; 527 } 528 } 529 530 return cnt; 531 } 532 533 // ////////////////////////////////////////////////////////////////////////// 534 // inner classes // 535 // ////////////////////////////////////////////////////////////////////////// 536 537 /** 538 * Inner class for accessing the data of one layer and creating a GML document from it. The class extends 539 * <tt>Thread</tt> and implements the run method, so that a parallel data accessing from several layers is possible. 540 * 541 * @version $Revision: 24806 $ 542 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 543 */ 544 public class ServiceInvoker implements Callable<Object> { 545 private Layer layer = null; 546 547 private AbstractDataSource datasource = null; 548 549 /** 550 * Creates a new ServiceInvoker object. 551 * 552 * @param layer 553 * @param datasource 554 */ 555 ServiceInvoker( Layer layer, AbstractDataSource datasource ) { 556 this.layer = layer; 557 this.datasource = datasource; 558 } 559 560 /** 561 * central method for access the data assigned to a data source 562 * 563 * @return result of feature info query 564 */ 565 public Object call() { 566 Object response = null; 567 if ( datasource != null ) { 568 Callable<Object> task = null; 569 try { 570 int type = datasource.getType(); 571 switch ( type ) { 572 case AbstractDataSource.LOCALWFS: 573 case AbstractDataSource.REMOTEWFS: { 574 OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource ); 575 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 576 break; 577 } 578 case AbstractDataSource.LOCALWCS: 579 case AbstractDataSource.REMOTEWCS: { 580 OGCWebServiceRequest request = GetMapServiceInvokerForNL.createGetCoverageRequest( datasource, 581 getMapRequest ); 582 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 583 break; 584 } 585 case AbstractDataSource.REMOTEWMS: { 586 OGCWebServiceRequest request = createGetFeatureInfo( datasource ); 587 task = new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 588 break; 589 } 590 case AbstractDataSource.DATABASE: { 591 DatabaseDataSource dbds = (DatabaseDataSource) datasource; 592 Envelope target = calcTargetArea( dbds, dbds.getNativeCRS().getPrefixedName() ); 593 594 for ( Dimension dim : configuration.getLayer( layer.getName() ).getDimension() ) { 595 if ( dim.getDefaultValue() != null ) { 596 if ( dim.getName().equals( "time" ) 597 && request.getGetMapRequestCopy().getDimTime() == null ) { 598 request.getGetMapRequestCopy().setDimTime( 599 new DimensionValues( 600 dim.getDefaultValue() ) ); 601 } else if ( dim.getName().equals( "elevation" ) 602 && request.getGetMapRequestCopy().getDimElev() == null ) { 603 request.getGetMapRequestCopy().setDimElev( 604 new DimensionValues( 605 dim.getDefaultValue() ) ); 606 } 607 } 608 } 609 610 task = new DoDatabaseQueryTask( (DatabaseDataSource) datasource, target, null, 611 datasource.getDimProps(), request.getGetMapRequestCopy() ); 612 break; 613 } 614 case AbstractDataSource.EXTERNALDATAACCESS: { 615 ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess(); 616 task = new DoExternalAccessTask( eda, request ); 617 break; 618 } 619 } 620 } catch ( Exception e ) { 621 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvoker: " + layer.getName(), 622 Messages.getMessage( "WMS_CREATE_QUERY" ) ); 623 response = exce; 624 LOG.logError( Messages.getMessage( "WMS_CREATE_QUERY" ) + ": " + e.getMessage(), e ); 625 throw new RuntimeException( e ); 626 } 627 628 try { 629 Executor executor = Executor.getInstance(); 630 Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 ); 631 response = handleResponse( o ); 632 } catch ( CancellationException e ) { 633 // exception can't be re-thrown because responsible GetMapHandler 634 // must collect all responses of all data sources 635 String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE", 636 new Integer( datasource.getRequestTimeLimit() ) ); 637 LOG.logError( s, e ); 638 if ( datasource.isFailOnException() ) { 639 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 640 response = exce; 641 } else { 642 response = null; 643 } 644 } catch ( Throwable t ) { 645 // exception can't be re-thrown because responsible GetMapHandler 646 // must collect all responses of all data sources 647 String s = Messages.getMessage( "WMS_ERRORDOSERVICE", t.getMessage() ); 648 LOG.logError( s, t ); 649 if ( datasource.isFailOnException() ) { 650 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 651 response = exce; 652 } else { 653 response = null; 654 } 655 } 656 657 } 658 659 return response; 660 661 } 662 663 /** 664 * creates a getFeature request considering the getMap request and the filter conditions defined in the 665 * submitted <tt>DataSource</tt> object. The request will be encapsulated within a <tt>OGCWebServiceEvent</tt>. 666 * 667 * @param ds 668 * @return GetFeature request object 669 */ 670 private GetFeature createGetFeatureRequest( LocalWFSDataSource ds ) 671 throws Exception { 672 673 WFSCapabilities capa; 674 if ( ds.getOGCWebService() instanceof WFService ) { 675 WFService se = (WFService) ds.getOGCWebService(); 676 capa = se.getCapabilities(); 677 } else { 678 RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService(); 679 capa = wfs.getWFSCapabilities(); 680 } 681 QualifiedName gn = ds.getName(); 682 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn ); 683 684 if ( ft == null ) { 685 throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) ); 686 } 687 String crs = ft.getDefaultSRS().toASCIIString(); 688 Envelope targetArea = calcTargetArea( ds, crs ); 689 690 // no filter condition has been defined 691 StringBuffer sb = new StringBuffer( 2000 ); 692 sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" ); 693 sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " ); 694 sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); 695 sb.append( "xmlns:wfs='http://www.opengis.net/wfs' " ); 696 sb.append( "xmlns:gml='http://www.opengis.net/gml' " ); 697 sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' ); 698 sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " ); 699 sb.append( "service='WFS' version='1.1.0' " ); 700 if ( ds.getType() == AbstractDataSource.LOCALWFS ) { 701 sb.append( "outputFormat='FEATURECOLLECTION'>" ); 702 } else { 703 sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" ); 704 } 705 sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" ); 706 707 Query query = ds.getQuery(); 708 709 // append <wfs:PropertyName> elements 710 if ( query != null && query.getPropertyNames() != null ) { 711 PropertyPath[] propertyNames = query.getPropertyNames(); 712 for ( PropertyPath path : propertyNames ) { 713 NamespaceContext nsContext = path.getNamespaceContext(); 714 sb.append( "<wfs:PropertyName" ); 715 Map<String, URI> namespaceMap = nsContext.getNamespaceMap(); 716 Iterator<String> prefixIter = namespaceMap.keySet().iterator(); 717 while ( prefixIter.hasNext() ) { 718 String prefix = prefixIter.next(); 719 if ( !CommonNamespaces.XMLNS_PREFIX.equals( prefix ) ) { 720 URI namespace = namespaceMap.get( prefix ); 721 sb.append( " xmlns:" + prefix + "=\"" + namespace + "\"" ); 722 } 723 } 724 sb.append( '>' ); 725 sb.append( path.getAsString() ); 726 sb.append( "</wfs:PropertyName>" ); 727 } 728 } 729 730 sb.append( "<ogc:Filter>" ); 731 if ( query == null ) { 732 // BBOX operation for speeding up the search at simple datasources 733 // like shapes 734 sb.append( "<ogc:BBOX><PropertyName>" ); 735 sb.append( ds.getGeometryProperty().getPrefixedName() ); 736 sb.append( "</PropertyName>" ); 737 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) ); 738 sb.append( "</ogc:BBOX>" ); 739 sb.append( "</ogc:Filter></Query></GetFeature>" ); 740 } else { 741 Filter filter = query.getFilter(); 742 743 sb.append( "<ogc:And>" ); 744 // BBOX operation for speeding up the search at simple datasources 745 // like shapes 746 sb.append( "<ogc:BBOX><PropertyName>" + ds.getGeometryProperty().getPrefixedName() ); 747 sb.append( "</PropertyName>" ); 748 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) ); 749 sb.append( "</ogc:BBOX>" ); 750 751 if ( filter instanceof ComplexFilter ) { 752 org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation(); 753 sb.append( op.toXML() ); 754 } else { 755 ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds(); 756 for ( int i = 0; i < featureIds.size(); i++ ) { 757 FeatureId fid = featureIds.get( i ); 758 sb.append( fid.toXML() ); 759 } 760 } 761 sb.append( "</ogc:And></ogc:Filter></Query></GetFeature>" ); 762 } 763 764 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 765 LOG.logDebug( "GetFeature-request:\n" + sb ); 766 } 767 768 // create dom representation of the request 769 StringReader sr = new StringReader( sb.toString() ); 770 Document doc = XMLTools.parse( sr ); 771 772 // create OGCWebServiceEvent object 773 IDGenerator idg = IDGenerator.getInstance(); 774 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() ); 775 776 return gfr; 777 } 778 779 /** 780 * calculates the target area for the getfeatureinfo request from the maps bounding box, the its size and the 781 * image coordinates of interest. An area is calculated instead of using a point because to consider 782 * uncertainties determining the point of interest 783 * 784 * @param ds 785 * <tt>DataSource</tt> of the layer that is requested for feature infos (each layer may be offered in 786 * its own crs) 787 * @param crs 788 */ 789 private Envelope calcTargetArea( AbstractDataSource ds, String crs ) 790 throws OGCWebServiceException { 791 792 int width = request.getGetMapRequestCopy().getWidth(); 793 int height = request.getGetMapRequestCopy().getHeight(); 794 int x = request.getClickPoint().x; 795 int y = request.getClickPoint().y; 796 797 Envelope bbox = request.getGetMapRequestCopy().getBoundingBox(); 798 799 // transform request bounding box to the coordinate reference 800 // system the WFS holds the data if requesting CRS and WFS-Data 801 // crs are different 802 Envelope tBbox = null; 803 try { 804 GeoTransform gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(), 805 bbox.getMax().getX(), bbox.getMax().getY(), 0, 0, 806 width - 1, height - 1 ); 807 double[] target = new double[4]; 808 int rad = configuration.getDeegreeParams().getFeatureInfoRadius(); 809 target[0] = gt.getSourceX( x - rad ); 810 target[1] = gt.getSourceY( y + rad ); 811 target[2] = gt.getSourceX( x + rad ); 812 target[3] = gt.getSourceY( y - rad ); 813 814 tBbox = GeometryFactory.createEnvelope( target[0], target[1], target[2], target[3], null ); 815 if ( !( crs.equalsIgnoreCase( request.getGetMapRequestCopy().getSrs() ) ) ) { 816 GeoTransformer transformer = new GeoTransformer( CRSFactory.create( crs ) ); 817 tBbox = transformer.transform( tBbox, reqCRS ); 818 } 819 820 } catch ( Exception e ) { 821 throw new OGCWebServiceException( e.toString() ); 822 } 823 824 return tBbox; 825 } 826 827 /** 828 * creates a GetFeatureInfo request for requesting a cascaded remote WMS The request will be encapsulated within 829 * a <tt>OGCWebServiceEvent</tt>. 830 * 831 * @param ds 832 * @return GetFeatureInfo request object 833 */ 834 private GetFeatureInfo createGetFeatureInfo( AbstractDataSource ds ) { 835 836 // create embbeded map request 837 GetMap gmr = ( (RemoteWMSDataSource) ds ).getGetMapRequest(); 838 839 String format = getMapRequest.getFormat(); 840 841 if ( gmr != null && !"%default%".equals( gmr.getFormat() ) ) { 842 format = gmr.getFormat(); 843 } 844 845 org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] lys = null; 846 lys = new org.deegree.ogcwebservices.wms.operation.GetMap.Layer[1]; 847 lys[0] = GetMap.createLayer( layer.getName(), "$DEFAULT" ); 848 849 if ( gmr != null && gmr.getLayers() != null ) { 850 lys = gmr.getLayers(); 851 } 852 Color bgColor = getMapRequest.getBGColor(); 853 if ( gmr != null && gmr.getBGColor() != null ) { 854 bgColor = gmr.getBGColor(); 855 } 856 Values time = getMapRequest.getTime(); 857 if ( gmr != null && gmr.getTime() != null ) { 858 time = gmr.getTime(); 859 } 860 Map<String, String> vendorSpecificParameter = getMapRequest.getVendorSpecificParameters(); 861 if ( gmr != null && gmr.getVendorSpecificParameters() != null 862 && gmr.getVendorSpecificParameters().size() > 0 ) { 863 vendorSpecificParameter = gmr.getVendorSpecificParameters(); 864 } 865 String version = "1.1.0"; 866 if ( gmr != null && gmr.getVersion() != null ) { 867 version = gmr.getVersion(); 868 } 869 Values elevation = getMapRequest.getElevation(); 870 if ( gmr != null && gmr.getElevation() != null ) { 871 elevation = gmr.getElevation(); 872 } 873 Map<String, Values> sampleDim = null; 874 if ( gmr != null && gmr.getSampleDimension() != null ) { 875 sampleDim = gmr.getSampleDimension(); 876 } 877 878 IDGenerator idg = IDGenerator.getInstance(); 879 gmr = GetMap.create( version, "" + idg.generateUniqueID(), lys, elevation, sampleDim, format, 880 getMapRequest.getWidth(), getMapRequest.getHeight(), getMapRequest.getSrs(), 881 getMapRequest.getBoundingBox(), getMapRequest.getTransparency(), bgColor, 882 getMapRequest.getExceptions(), time, null, null, vendorSpecificParameter ); 883 884 // create GetFeatureInfo request for cascaded/remote WMS 885 String[] queryLayers = new String[] { ds.getName().getPrefixedName() }; 886 GetFeatureInfo req = GetFeatureInfo.create( version, this.toString(), queryLayers, gmr, 887 "application/vnd.ogc.gml", request.getFeatureCount(), 888 request.getClickPoint(), request.getExceptions(), null, 889 request.getVendorSpecificParameters() ); 890 891 try { 892 LOG.logDebug( "cascaded GetFeatureInfo request: ", req.getRequestParameter() ); 893 } catch ( OGCWebServiceException e ) { 894 LOG.logError( e.getMessage(), e ); 895 } 896 897 return req; 898 } 899 900 /** 901 * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS implementation accessed by 902 * this class is able to return the result of a request by calling the write-method. 903 * 904 * @param result 905 * to a GetXXX request 906 * @return the response object 907 * @throws Exception 908 */ 909 private Object handleResponse( Object result ) 910 throws Exception { 911 Object response = null; 912 if ( result instanceof FeatureResult ) { 913 response = handleGetFeatureResponse( (FeatureResult) result ); 914 } else if ( result instanceof ResultCoverage ) { 915 response = handleGetCoverageResponse( (ResultCoverage) result ); 916 } else if ( result instanceof GetFeatureInfoResult ) { 917 response = handleGetFeatureInfoResult( (GetFeatureInfoResult) result ); 918 } else { 919 throw new Exception( Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ) ); 920 } 921 return response; 922 } 923 924 /** 925 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from 926 * it 927 * 928 * @param response 929 * @return the response objects 930 * @throws Exception 931 */ 932 private Object handleGetFeatureResponse( FeatureResult response ) 933 throws Exception { 934 FeatureCollection fc = null; 935 936 Object o = response.getResponse(); 937 if ( o instanceof FeatureCollection ) { 938 fc = (FeatureCollection) o; 939 } else if ( o.getClass() == byte[].class ) { 940 Reader reader = new InputStreamReader( new ByteArrayInputStream( (byte[]) o ) ); 941 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument(); 942 doc.load( reader, XMLFragment.DEFAULT_URL ); 943 fc = doc.parse(); 944 } else { 945 throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) ); 946 } 947 return fc; 948 } 949 950 /** 951 * 952 * @param res 953 */ 954 private Object handleGetFeatureInfoResult( GetFeatureInfoResult res ) 955 throws Exception { 956 957 FeatureCollection fc = null; 958 StringReader sr = new StringReader( res.getFeatureInfo() ); 959 XMLFragment xml = new XMLFragment( sr, XMLFragment.DEFAULT_URL ); 960 961 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 962 LOG.logDebug( "GetFeature-response (before transformation): " + xml.getAsPrettyString() ); 963 } 964 965 if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) { 966 URL url = ( (RemoteWMSDataSource) datasource ).getFeatureInfoTransform(); 967 if ( url != null ) { 968 // transform incoming GML/XML to a GML application schema 969 // that is understood by deegree 970 XSLTDocument xslt = new XSLTDocument(); 971 xslt.load( url ); 972 xml = xslt.transform( xml, null, null, null ); 973 } 974 } 975 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument(); 976 doc.setRootElement( xml.getRootElement() ); 977 fc = doc.parse(); 978 return fc; 979 } 980 981 private Object handleGetCoverageResponse( ResultCoverage response ) 982 throws OGCWebServiceException { 983 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage(); 984 if ( gc != null ) { 985 BufferedImage bi = gc.getAsImage( -1, -1 ); 986 987 float[][] data = new Image2RawData( bi ).parse(); 988 989 double scaleX = (double) data[0].length / (double) getMapRequest.getWidth(); 990 double scaleY = (double) data.length / (double) getMapRequest.getHeight(); 991 int pxSizeX = (int) Math.round( scaleX ); 992 int pxSizeY = (int) Math.round( scaleY ); 993 if ( pxSizeX == 0 ) { 994 pxSizeX = 1; 995 } 996 if ( pxSizeY == 0 ) { 997 pxSizeY = 1; 998 } 999 1000 LOG.logDebug( "Size of grid coverage is " + data[0].length + "x" + data.length + "." ); 1001 LOG.logDebug( "Returning an area of " + pxSizeX + "x" + pxSizeY + " pixels." ); 1002 1003 int ix = (int) ( request.getClickPoint().x * scaleX ) - pxSizeX / 2; 1004 int iy = (int) ( request.getClickPoint().y * scaleY ) - pxSizeY / 2; 1005 1006 // some checks to avoid areas that are not requestable 1007 if ( ix < 0 ) { 1008 ix = 0; 1009 } 1010 if ( iy < 0 ) { 1011 iy = 0; 1012 } 1013 if ( ix >= ( data[0].length - pxSizeX ) ) { 1014 ix = data[0].length - pxSizeX - 1; 1015 } 1016 if ( iy >= ( data.length - pxSizeY ) ) { 1017 iy = data.length - pxSizeY - 1; 1018 } 1019 1020 FeatureCollection fc = FeatureFactory.createFeatureCollection( gc.getCoverageOffering().getName(), 1021 pxSizeX * pxSizeY ); 1022 1023 PropertyType pt = null; 1024 try { 1025 pt = FeatureFactory.createPropertyType( VALUE, new QualifiedName( "xsd", "double", 1026 CommonNamespaces.XSNS ), 1, 1 ); 1027 } catch ( UnknownTypeException e ) { 1028 LOG.logError( "The xsd:double type is not known?!? Get a new deegree.jar!", e ); 1029 } 1030 FeatureType ft = FeatureFactory.createFeatureType( gc.getCoverageOffering().getName(), false, 1031 new PropertyType[] { pt } ); 1032 1033 for ( int x = ix; x < ix + pxSizeX; ++x ) { 1034 for ( int y = iy; y < iy + pxSizeY; ++y ) { 1035 FeatureProperty p = FeatureFactory.createFeatureProperty( VALUE, new Double( data[y][x] ) ); 1036 Feature f = FeatureFactory.createFeature( "ID_faked_for_" + x + "x" + y, ft, 1037 new FeatureProperty[] { p } ); 1038 fc.add( f ); 1039 } 1040 } 1041 1042 return fc; 1043 } 1044 1045 throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE", 1046 datasource.getName() ) ); 1047 1048 } 1049 1050 } 1051 1052 }