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