001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wms/DefaultGetFeatureInfoHandler.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2007 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/deegree/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstr. 19 030 53177 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 043 ---------------------------------------------------------------------------*/ 044 package org.deegree.ogcwebservices.wms; 045 046 import java.awt.Color; 047 import java.awt.image.BufferedImage; 048 import java.io.ByteArrayInputStream; 049 import java.io.InputStreamReader; 050 import java.io.Reader; 051 import java.io.StringReader; 052 import java.net.URI; 053 import java.net.URL; 054 import java.util.ArrayList; 055 import java.util.Arrays; 056 import java.util.Iterator; 057 import java.util.List; 058 import java.util.Map; 059 import java.util.concurrent.Callable; 060 import java.util.concurrent.CancellationException; 061 062 import org.deegree.datatypes.QualifiedName; 063 import org.deegree.datatypes.Types; 064 import org.deegree.datatypes.UnknownTypeException; 065 import org.deegree.datatypes.values.Values; 066 import org.deegree.framework.concurrent.ExecutionFinishedEvent; 067 import org.deegree.framework.concurrent.ExecutionFinishedListener; 068 import org.deegree.framework.concurrent.Executor; 069 import org.deegree.framework.log.ILogger; 070 import org.deegree.framework.log.LoggerFactory; 071 import org.deegree.framework.util.CharsetUtils; 072 import org.deegree.framework.util.IDGenerator; 073 import org.deegree.framework.util.MapUtils; 074 import org.deegree.framework.util.NetWorker; 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.crs.IGeoTransformer; 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.OGCWebService; 106 import org.deegree.ogcwebservices.OGCWebServiceException; 107 import org.deegree.ogcwebservices.OGCWebServiceRequest; 108 import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage; 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.Layer; 116 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 117 import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource; 118 import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource; 119 import org.deegree.ogcwebservices.wms.configuration.WMSConfigurationType; 120 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo; 121 import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult; 122 import org.deegree.ogcwebservices.wms.operation.GetMap; 123 import org.deegree.ogcwebservices.wms.operation.WMSProtocolFactory; 124 import org.deegree.processing.raster.converter.Image2RawData; 125 import org.w3c.dom.Document; 126 127 /** 128 * 129 * 130 * 131 * @version $Revision: 7978 $ 132 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 133 * @author last edited by: $Author: aschmitz $ 134 * 135 * @version 1.0. $Revision: 7978 $, $Date: 2007-08-10 13:04:16 +0200 (Fr, 10 Aug 2007) $ 136 * 137 */ 138 class DefaultGetFeatureInfoHandler implements GetFeatureInfoHandler, ExecutionFinishedListener<Object[]> { 139 140 protected static final ILogger LOG = LoggerFactory.getLogger( DefaultGetFeatureInfoHandler.class ); 141 142 private static final double DEFAULT_PIXEL_SIZE = 0.00028; 143 144 protected GetFeatureInfo request = null; 145 146 protected GetMap getMapRequest = null; 147 148 protected WMSConfigurationType configuration = null; 149 150 // collects the reponse for each layer 151 private Object[] featCol = null; 152 153 // scale of the map 154 protected double scale = 0; 155 156 // holds the number of request services that have responsed 157 private int count = 0; 158 159 // CRS of the request 160 protected CoordinateSystem reqCRS = null; 161 162 protected static final QualifiedName VALUE = new QualifiedName( "value" ); 163 164 /** 165 * Creates a new GetMapHandler object. 166 * 167 * @param capabilities 168 * @param request 169 * request to perform 170 * @throws OGCWebServiceException 171 */ 172 public DefaultGetFeatureInfoHandler( WMSConfigurationType capabilities, GetFeatureInfo request ) 173 throws OGCWebServiceException { 174 175 this.request = request; 176 this.configuration = capabilities; 177 getMapRequest = request.getGetMapRequestCopy(); 178 try { 179 reqCRS = CRSFactory.create( getMapRequest.getSrs() ); 180 if ( reqCRS == null ) { 181 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) ); 182 } 183 } catch ( Exception e ) { 184 throw new InvalidSRSException( Messages.getMessage( "WMS_UNKNOWN_CRS", getMapRequest.getSrs() ) ); 185 } 186 try { 187 Envelope bbox = getMapRequest.getBoundingBox(); 188 scale = MapUtils.calcScale( getMapRequest.getWidth(), getMapRequest.getHeight(), bbox, reqCRS, 189 DEFAULT_PIXEL_SIZE ); 190 } catch ( Exception e ) { 191 LOG.logDebug( e.getLocalizedMessage(), e ); 192 throw new OGCWebServiceException( Messages.getMessage( "WMS_SCALECALC", e ) ); 193 } 194 195 } 196 197 /** 198 * increases the counter variable that holds the number of services that has sent a response. 199 * All data are available if the counter value equals the number of requested layers. 200 */ 201 protected synchronized void increaseCounter() { 202 count++; 203 } 204 205 /** 206 * performs a GetFeatureInfo request and retruns the result encapsulated within a 207 * <tt>WMSFeatureInfoResponse</tt> object. 208 * <p> 209 * The method throws an WebServiceException that only shall be thrown if an fatal error occurs 210 * that makes it imposible to return a result. If something wents wrong performing the request 211 * (none fatal error) The exception shall be encapsulated within the response object to be 212 * returned to the client as requested (GetFeatureInfo-Request EXCEPTION-Parameter). 213 * 214 * <p> 215 * All sublayers of the queried layer will be added automatically. Non-queryable sublayers are 216 * then ignored in the response. 217 * </p> 218 * 219 * @return response to the GetFeatureInfo response 220 */ 221 public GetFeatureInfoResult performGetFeatureInfo() 222 throws OGCWebServiceException { 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 // sublayers 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 // sublayers are ignored). 251 allLayers.addAll( Arrays.asList( layer.getLayer() ) ); 252 } 253 254 Layer[] layerList = allLayers.toArray( new Layer[allLayers.size()] ); 255 256 // there must be one feature collection for each requested layer 257 int cnt = countNumberOfQueryableDataSources( layerList ); 258 featCol = new Object[cnt]; 259 260 // invokes the data supplyer for each layer in an independ thread 261 int kk = 0; 262 for ( int i = 0; i < layerList.length; i++ ) { 263 if ( validate( layerList[i] ) ) { 264 AbstractDataSource datasource[] = layerList[i].getDataSource(); 265 for ( int j = 0; j < datasource.length; j++ ) { 266 if ( datasource[j].isQueryable() && isValidArea( datasource[j].getValidArea() ) ) { 267 ServiceInvoker si = new ServiceInvoker( layerList[i], datasource[j], kk++ ); 268 ServiceInvokerTask task = new ServiceInvokerTask( si ); 269 Executor.getInstance().performAsynchronously( task, this ); 270 } 271 } 272 } else { 273 // set feature collection to null if no data are available for the requested 274 // area and/or scale. This will cause this index position will be ignored 275 // when creating the final result 276 featCol[kk++] = null; 277 increaseCounter(); 278 } 279 } 280 281 // waits until the requested layers are available as <tt>DisplayElements</tt> 282 // or the time limit has been reached. 283 // TODO 284 // substitue by an event based approach 285 try { 286 waitForFinish(); 287 } catch ( Exception e ) { 288 return createExceptionResponse( e ); 289 } 290 291 GetFeatureInfoResult res = createFeatureInfoResponse(); 292 293 return res; 294 } 295 296 /** 297 * returns the number of datasources assigned to the queried layers that are queryable 298 * 299 * @param layerList 300 * @return the number 301 */ 302 private int countNumberOfQueryableDataSources( Layer[] layerList ) { 303 int cnt = 0; 304 for ( int i = 0; i < layerList.length; i++ ) { 305 AbstractDataSource[] ds = layerList[i].getDataSource(); 306 for ( int j = 0; j < ds.length; j++ ) { 307 if ( ds[j].isQueryable() && isValidArea( ds[j].getValidArea() ) ) { 308 cnt++; 309 } 310 } 311 } 312 return cnt; 313 } 314 315 /** 316 * returns true if the requested boundingbox intersects with the valid area of a datasource 317 * 318 * @param validArea 319 */ 320 private boolean isValidArea( Geometry validArea ) { 321 322 if ( validArea != null ) { 323 try { 324 Envelope env = request.getGetMapRequestCopy().getBoundingBox(); 325 Geometry geom = GeometryFactory.createSurface( env, reqCRS ); 326 if ( !reqCRS.getName().equals( validArea.getCoordinateSystem().getName() ) ) { 327 // if requested CRS is not identical to the CRS of the valid area 328 // a transformation must be performed before intersection can 329 // be checked 330 IGeoTransformer gt = new GeoTransformer( validArea.getCoordinateSystem() ); 331 geom = gt.transform( geom ); 332 } 333 return geom.intersects( validArea ); 334 } catch ( Exception e ) { 335 // should never happen 336 LOG.logError( "Could not validate WMS datasource area", e ); 337 } 338 } 339 return true; 340 } 341 342 /** 343 * validates if the requested layer matches the conditions of the request if not a 344 * <tt>WebServiceException</tt> will be thrown. If the layer matches the request, but isn't 345 * able to deviever data for the requested area and/or scale false will be returned. If the 346 * layer matches the request and contains data for the requested area and/or scale true will be 347 * returned. 348 * 349 * @param layer 350 * layer as defined at the capabilities/configuration 351 */ 352 private boolean validate( Layer layer ) 353 throws OGCWebServiceException { 354 355 // why the layer can be null here is not known, but just in case: 356 String name = ( layer == null ) ? "" : layer.getName(); 357 358 // check for valid coordinated reference system 359 String[] srs = layer.getSrs(); 360 boolean tmp = false; 361 for ( int i = 0; i < srs.length; i++ ) { 362 if ( srs[i].equalsIgnoreCase( getMapRequest.getSrs() ) ) { 363 tmp = true; 364 break; 365 } 366 } 367 368 if ( !tmp ) { 369 throw new InvalidSRSException( Messages.getMessage( "WMS_INVALIDSRS", name, getMapRequest.getSrs() ) ); 370 } 371 372 // check bounding box 373 try { 374 375 Envelope bbox = getMapRequest.getBoundingBox(); 376 Envelope layerBbox = layer.getLatLonBoundingBox(); 377 if ( !getMapRequest.getSrs().equalsIgnoreCase( "EPSG:4326" ) ) { 378 // transform the bounding box of the request to EPSG:4326 379 IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) ); 380 bbox = gt.transform( bbox, reqCRS ); 381 } 382 383 if ( !bbox.intersects( layerBbox ) ) { 384 return false; 385 } 386 387 } catch ( Exception e ) { 388 throw new OGCWebServiceException( Messages.getMessage( "WMS_BBOXCOMPARSION" ) ); 389 } 390 391 return true; 392 } 393 394 /** 395 * creates a <tt>GetFeatureInfoResult</tt> containing an <tt>OGCWebServiceException</tt> 396 * 397 * @param e 398 * exception to encapsulate into the response 399 */ 400 private GetFeatureInfoResult createExceptionResponse( Exception e ) { 401 402 OGCWebServiceException exce = null; 403 404 // default --> application/vnd.ogc.se_xml 405 exce = new OGCWebServiceException( getClass().getName(), e.getMessage() ); 406 407 GetFeatureInfoResult res = WMSProtocolFactory.createGetFeatureInfoResponse( request, exce, null ); 408 409 return res; 410 } 411 412 /** 413 * waits until the requested layers are available as <tt>DisplayElements</tt> or the time 414 * limit has been reached. If the waiting is terminated by reaching the time limit an 415 * <tt>WebServiceException</tt> will be thrown to indicated that the request couldn't be 416 * performed correctly. 417 * 418 * @throws WebServiceException 419 * if the time limit has been reached 420 */ 421 private void waitForFinish() 422 throws OGCWebServiceException, Exception { 423 424 // subtract 1 second for architecture overhead and image creation 425 long timeLimit = 1000 * ( configuration.getDeegreeParams().getRequestTimeLimit() - 1 ); 426 // long timeLimit = 1000 * 100; 427 long runTime = 0; 428 429 while ( count < featCol.length ) { 430 try { 431 Thread.sleep( 100 ); 432 } catch ( Exception e ) { 433 LOG.logError( "WMS_WAITING_LOOP", e ); 434 throw e; 435 } 436 437 runTime += 100; 438 439 // finish loop after if request performing hasn't been completed 440 // after the time limit is reached 441 if ( runTime > timeLimit ) { 442 throw new OGCWebServiceException( Messages.getMessage( "WMS_TIMEOUT" ) ); 443 } 444 } 445 446 } 447 448 /** 449 * will be called each time a datasource has been read 450 * 451 * @param returnValue 452 */ 453 public synchronized void executionFinished( ExecutionFinishedEvent<Object[]> returnValue ) { 454 Object[] o = null; 455 try { 456 o = returnValue.getResult(); 457 } catch ( Throwable t ) { 458 LOG.logError( "WMS_GETFEATURE_EXCEPTION", t ); 459 } 460 featCol[( (Integer) o[0] ).intValue()] = o[1]; 461 increaseCounter(); 462 } 463 464 /** 465 * generates the desired output from the GMLs 466 * 467 * @return the result object 468 * @throws OGCWebServiceException 469 */ 470 private GetFeatureInfoResult createFeatureInfoResponse() 471 throws OGCWebServiceException { 472 473 Envelope bbox = getMapRequest.getBoundingBox(); 474 475 StringBuffer sb = new StringBuffer( 20000 ); 476 sb.append( "<ll:FeatureCollection " ); 477 478 URL schemaLoc = configuration.getDeegreeParams().getFeatureSchemaLocation(); 479 if ( schemaLoc != null ) { 480 sb.append( "xsi:schemaLocation='" ); 481 sb.append( configuration.getDeegreeParams().getFeatureSchemaNamespace() ); 482 sb.append( " " ); 483 sb.append( schemaLoc.toExternalForm() ); 484 sb.append( "'" ); 485 } 486 487 sb.append( " xmlns:gml='http://www.opengis.net/gml' " ); 488 sb.append( "xmlns:ll='http://www.lat-lon.de' " ); 489 sb.append( "xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " ); 490 URL url = configuration.getDeegreeParams().getSchemaLocation(); 491 if ( url != null ) { 492 sb.append( "xsi:schemaLocation='" ); 493 sb.append( "http://www.lat-lon.de " + NetWorker.url2String( url ) + "'" ); 494 } 495 sb.append( "><gml:boundedBy>" ); 496 sb.append( "<gml:Box srsName='" + getMapRequest.getSrs() + "'>" ); 497 sb.append( "<gml:coordinates>" + bbox.getMin().getX() + "," ); 498 sb.append( bbox.getMin().getY() + " " + bbox.getMax().getX() + "," ); 499 sb.append( bbox.getMax().getY() + "</gml:coordinates>" ); 500 sb.append( "</gml:Box></gml:boundedBy>" ); 501 502 int cnt = 0; 503 504 for ( int i = 0; i < featCol.length; i++ ) { 505 if ( featCol[i] instanceof OGCWebServiceException ) { 506 throw (OGCWebServiceException) featCol[i]; 507 } 508 FeatureCollection fc = (FeatureCollection) featCol[i]; 509 cnt = appendFeatureCollection( fc, sb, cnt ); 510 511 // if ( cnt >= request.getFeatureCount() ) break; 512 } 513 sb.append( "</ll:FeatureCollection>" ); 514 515 GetFeatureInfoResult response = WMSProtocolFactory.createGetFeatureInfoResponse( request, null, sb.toString() ); 516 517 return response; 518 } 519 520 /** 521 * 522 * @param col 523 * @param sb 524 * @param cnt 525 * @return a counter, probably the same that is given as argument 526 */ 527 private int appendFeatureCollection( FeatureCollection col, StringBuffer sb, int cnt ) { 528 529 Feature[] feat = col.toArray(); 530 if ( feat != null ) { 531 for ( int j = 0; j < feat.length; j++ ) { 532 FeatureType ft = feat[j].getFeatureType(); 533 PropertyType[] ftp = ft.getProperties(); 534 cnt++; 535 sb.append( "<gml:featureMember>" ); 536 sb.append( "<ll:" + ft.getName().getLocalName() ); 537 sb.append( " fid='" + feat[j].getId().replace( ' ', '_' ) + "'>" ); 538 for ( int i = 0; i < ftp.length; i++ ) { 539 if ( ftp[i].getType() != Types.GEOMETRY && ftp[i].getType() != Types.POINT 540 && ftp[i].getType() != Types.CURVE && ftp[i].getType() != Types.SURFACE 541 && ftp[i].getType() != Types.MULTIPOINT && ftp[i].getType() != Types.MULTICURVE 542 && ftp[i].getType() != Types.MULTISURFACE ) { 543 544 FeatureProperty[] props = feat[j].getProperties( ftp[i].getName() ); 545 if ( props != null ) { 546 for ( FeatureProperty property : props ) { 547 Object value = property.getValue(); 548 sb.append( "<ll:" + ftp[i].getName().getLocalName() + ">" ); 549 if ( value instanceof FeatureCollection ) { 550 FeatureCollection fc = (FeatureCollection) value; 551 appendFeatureCollection( fc, sb, cnt ); 552 } else { 553 sb.append( "<![CDATA[" ).append( value ).append( "]]>" ); 554 } 555 sb.append( "</ll:" + ftp[i].getName().getLocalName() + ">" ); 556 } 557 } 558 } 559 } 560 sb.append( "</ll:" + ft.getName().getLocalName() + ">" ); 561 sb.append( "</gml:featureMember>" ); 562 if ( cnt >= request.getFeatureCount() ) 563 break; 564 } 565 } 566 567 return cnt; 568 } 569 570 // ////////////////////////////////////////////////////////////////////////// 571 // inner classes // 572 // ////////////////////////////////////////////////////////////////////////// 573 574 /** 575 * Inner class for accessing the data of one layer and creating a GML document from it. The 576 * class extends <tt>Thread</tt> and implements the run method, so that a parallel data 577 * accessing from several layers is possible. 578 * 579 * @version $Revision: 7978 $ 580 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 581 */ 582 public class ServiceInvoker { 583 private Layer layer = null; 584 585 private int index = 0; 586 587 private AbstractDataSource datasource = null; 588 589 /** 590 * Creates a new ServiceInvoker object. 591 * 592 * @param layer 593 * @param datasource 594 * @param index 595 * index of the requested layer 596 */ 597 ServiceInvoker( Layer layer, AbstractDataSource datasource, int index ) { 598 this.layer = layer; 599 this.index = index; 600 this.datasource = datasource; 601 } 602 603 /** 604 * central method for access the data assigned to a datasource 605 * 606 * @return result of feature info query 607 */ 608 public Object run() { 609 Object response = null; 610 if ( datasource != null ) { 611 OGCWebServiceRequest request = null; 612 try { 613 int type = datasource.getType(); 614 switch ( type ) { 615 case AbstractDataSource.LOCALWFS: 616 case AbstractDataSource.REMOTEWFS: { 617 request = createGetFeatureRequest( (LocalWFSDataSource) datasource ); 618 break; 619 } 620 case AbstractDataSource.LOCALWCS: 621 case AbstractDataSource.REMOTEWCS: { 622 request = GetMapServiceInvokerForNL.createGetCoverageRequest( datasource, getMapRequest ); 623 break; 624 } 625 case AbstractDataSource.REMOTEWMS: { 626 request = createGetFeatureInfo( datasource ); 627 break; 628 } 629 } 630 } catch ( Exception e ) { 631 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvoker: " + layer.getName(), 632 Messages.getMessage( "WMS_CREATE_QUERY" ) ); 633 response = new Object[] { new Integer( index ), exce }; 634 LOG.logError( Messages.getMessage( "WMS_CREATE_QUERY" ) + ": " + e.getMessage(), e ); 635 throw new RuntimeException( e ); 636 } 637 638 try { 639 Executor executor = Executor.getInstance(); 640 DoServiceTask task = new DoServiceTask( datasource.getOGCWebService(), request ); 641 Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 ); 642 response = handleResponse( o ); 643 } catch ( CancellationException e ) { 644 // exception can't be re-thrown because responsible GetMapHandler 645 // must collect all responses of all datasources 646 String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE", 647 new Integer( datasource.getRequestTimeLimit() ) ); 648 LOG.logError( s, e ); 649 if ( datasource.isFailOnException() ) { 650 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 651 response = new Object[] { new Integer( index ), exce }; 652 } else { 653 response = new Object[] { new Integer( index ), null }; 654 } 655 } catch ( Throwable t ) { 656 // exception can't be re-thrown because responsible GetMapHandler 657 // must collect all responses of all datasources 658 String s = Messages.getMessage( "WMS_ERRORDOSERVICE", t.getMessage() ); 659 LOG.logError( s, t ); 660 if ( datasource.isFailOnException() ) { 661 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 662 response = new Object[] { new Integer( index ), exce }; 663 } else { 664 response = new Object[] { new Integer( index ), null }; 665 } 666 } 667 668 } 669 670 return response; 671 672 } 673 674 /** 675 * creates a getFeature request considering the getMap request and the filterconditions 676 * defined in the submitted <tt>DataSource</tt> object. The request will be encapsualted 677 * within a <tt>OGCWebServiceEvent</tt>. 678 * 679 * @param ds 680 * @return GetFeature request object 681 */ 682 private GetFeature createGetFeatureRequest( LocalWFSDataSource ds ) 683 throws Exception { 684 685 Envelope targetArea = calcTargetArea( ds ); 686 687 // no filter condition has been defined 688 StringBuffer sb = new StringBuffer( 2000 ); 689 sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" ); 690 sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " ); 691 sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); 692 sb.append( "xmlns:wfs='http://www.opengis.net/wfs' " ); 693 sb.append( "xmlns:gml='http://www.opengis.net/gml' " ); 694 sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' ); 695 sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " ); 696 sb.append( "service='WFS' version='1.1.0' " ); 697 sb.append( "outputFormat='FEATURECOLLECTION'>" ); 698 sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" ); 699 700 Query query = ds.getQuery(); 701 702 // append <wfs:PropertyName> elements 703 if ( query != null && query.getPropertyNames() != null ) { 704 PropertyPath[] propertyNames = query.getPropertyNames(); 705 for ( PropertyPath path : propertyNames ) { 706 NamespaceContext nsContext = path.getNamespaceContext(); 707 sb.append( "<wfs:PropertyName" ); 708 Map<String, URI> namespaceMap = nsContext.getNamespaceMap(); 709 Iterator<String> prefixIter = namespaceMap.keySet().iterator(); 710 while ( prefixIter.hasNext() ) { 711 String prefix = prefixIter.next(); 712 if ( !CommonNamespaces.XMLNS_PREFIX.equals( prefix ) ) { 713 URI namespace = namespaceMap.get( prefix ); 714 sb.append( " xmlns:" + prefix + "=\"" + namespace + "\"" ); 715 } 716 } 717 sb.append( '>' ); 718 sb.append( path.getAsString() ); 719 sb.append( "</wfs:PropertyName>" ); 720 } 721 } 722 723 sb.append( "<ogc:Filter>" ); 724 if ( query == null ) { 725 // BBOX operation for speeding up the search at simple datasources 726 // like shapes 727 sb.append( "<ogc:BBOX><PropertyName>" ); 728 sb.append( ds.getGeometryProperty().getPrefixedName() ); 729 sb.append( "</PropertyName>" ); 730 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) ); 731 sb.append( "</ogc:BBOX>" ); 732 sb.append( "</ogc:Filter></Query></GetFeature>" ); 733 } else { 734 Filter filter = query.getFilter(); 735 736 sb.append( "<ogc:And>" ); 737 // BBOX operation for speeding up the search at simple datasources 738 // like shapes 739 sb.append( "<ogc:BBOX><PropertyName>" + ds.getGeometryProperty().getPrefixedName() ); 740 sb.append( "</PropertyName>" ); 741 sb.append( GMLGeometryAdapter.exportAsBox( targetArea ) ); 742 sb.append( "</ogc:BBOX>" ); 743 744 if ( filter instanceof ComplexFilter ) { 745 org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation(); 746 sb.append( op.toXML() ); 747 } else { 748 ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds(); 749 for ( int i = 0; i < featureIds.size(); i++ ) { 750 FeatureId fid = featureIds.get( i ); 751 sb.append( fid.toXML() ); 752 } 753 } 754 sb.append( "</ogc:And></ogc:Filter></Query></GetFeature>" ); 755 } 756 757 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 758 LOG.logDebug( "GetFeature-request:\n" + sb ); 759 } 760 761 // create dom representation of the request 762 StringReader sr = new StringReader( sb.toString() ); 763 Document doc = XMLTools.parse( sr ); 764 765 // create OGCWebServiceEvent object 766 IDGenerator idg = IDGenerator.getInstance(); 767 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() ); 768 769 return gfr; 770 } 771 772 /** 773 * calculates the target area for the getfeatureinfo request from the maps bounding box, the 774 * its size and the image coordinates of interest. An area is calculated instead of using a 775 * point because to consider uncertainties determining the point of interest 776 * 777 * @param ds 778 * <tt>DataSource</tt> of the layer that is requested for feature infos (each 779 * layer may be offered in its own crs) 780 */ 781 private Envelope calcTargetArea( AbstractDataSource ds ) 782 throws OGCWebServiceException { 783 784 int width = request.getGetMapRequestCopy().getWidth(); 785 int height = request.getGetMapRequestCopy().getHeight(); 786 int x = request.getClickPoint().x; 787 int y = request.getClickPoint().y; 788 789 Envelope bbox = request.getGetMapRequestCopy().getBoundingBox(); 790 791 // transform request bounding box to the coordinate reference 792 // system the WFS holds the data if requesting CRS and WFS-Data 793 // crs are different 794 // WFService se = (WFService)ds.getOGCWebService(); 795 // WFSCapabilities capa = (WFSCapabilities)se.getWFSCapabilities(); 796 // 797 // org.deegree.ogcwebservices.wfs.capabilities.FeatureType ft = 798 // capa.getFeatureTypeList().getFeatureType( ds.getName() ); 799 WFService se = (WFService) ds.getOGCWebService(); 800 WFSCapabilities capa = se.getCapabilities(); 801 QualifiedName gn = ds.getName(); 802 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn ); 803 804 if ( ft == null ) { 805 throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) ); 806 } 807 String crs = ft.getDefaultSRS().toASCIIString(); 808 Envelope tBbox = null; 809 try { 810 GeoTransform gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(), 811 bbox.getMax().getX(), bbox.getMax().getY(), 0, 0, 812 width - 1, height - 1 ); 813 double[] target = new double[4]; 814 int rad = configuration.getDeegreeParams().getFeatureInfoRadius(); 815 target[0] = gt.getSourceX( x - rad ); 816 target[1] = gt.getSourceY( y + rad ); 817 target[2] = gt.getSourceX( x + rad ); 818 target[3] = gt.getSourceY( y - rad ); 819 820 tBbox = GeometryFactory.createEnvelope( target[0], target[1], target[2], target[3], null ); 821 if ( !( crs.equalsIgnoreCase( request.getGetMapRequestCopy().getSrs() ) ) ) { 822 IGeoTransformer transformer = new GeoTransformer( CRSFactory.create( crs ) ); 823 tBbox = transformer.transform( tBbox, reqCRS ); 824 } 825 826 } catch ( Exception e ) { 827 throw new OGCWebServiceException( e.toString() ); 828 } 829 830 return tBbox; 831 } 832 833 /** 834 * creates a GetFeatureInfo request for requesting a cascaded remote WMS The request will be 835 * encapsualted within a <tt>OGCWebServiceEvent</tt>. 836 * 837 * @param ds 838 * @return GetFeatureInfo request object 839 */ 840 private GetFeatureInfo createGetFeatureInfo( AbstractDataSource ds ) { 841 842 // create embbeded map request 843 GetMap gmr = ( (RemoteWMSDataSource) ds ).getGetMapRequest(); 844 845 String format = getMapRequest.getFormat(); 846 847 if ( gmr != null && !"%default%".equals( gmr.getFormat() ) ) { 848 format = gmr.getFormat(); 849 } 850 851 org.deegree.ogcwebservices.wms.operation.GetMap.Layer[] lys = null; 852 lys = new org.deegree.ogcwebservices.wms.operation.GetMap.Layer[1]; 853 lys[0] = GetMap.createLayer( layer.getName(), "$DEFAULT" ); 854 855 if ( gmr != null && gmr.getLayers() != null ) { 856 lys = gmr.getLayers(); 857 } 858 Color bgColor = getMapRequest.getBGColor(); 859 if ( gmr != null && gmr.getBGColor() != null ) { 860 bgColor = gmr.getBGColor(); 861 } 862 Values time = getMapRequest.getTime(); 863 if ( gmr != null && gmr.getTime() != null ) { 864 time = gmr.getTime(); 865 } 866 Map<String, String> vendorSpecificParameter = getMapRequest.getVendorSpecificParameters(); 867 if ( gmr != null && gmr.getVendorSpecificParameters() != null 868 && gmr.getVendorSpecificParameters().size() > 0 ) { 869 vendorSpecificParameter = gmr.getVendorSpecificParameters(); 870 } 871 String version = "1.1.0"; 872 if ( gmr != null && gmr.getVersion() != null ) { 873 version = gmr.getFormat(); 874 } 875 Values elevation = getMapRequest.getElevation(); 876 if ( gmr != null && gmr.getElevation() != null ) { 877 elevation = gmr.getElevation(); 878 } 879 Map<String, Values> sampleDim = null; 880 if ( gmr != null && gmr.getSampleDimension() != null ) { 881 sampleDim = gmr.getSampleDimension(); 882 } 883 884 IDGenerator idg = IDGenerator.getInstance(); 885 gmr = GetMap.create( version, "" + idg.generateUniqueID(), lys, elevation, sampleDim, format, 886 getMapRequest.getWidth(), getMapRequest.getHeight(), getMapRequest.getSrs(), 887 getMapRequest.getBoundingBox(), getMapRequest.getTransparency(), bgColor, 888 getMapRequest.getExceptions(), time, null, null, vendorSpecificParameter ); 889 890 // create GetFeatureInfo request for cascaded/remote WMS 891 String[] queryLayers = new String[] { ds.getName().getPrefixedName() }; 892 GetFeatureInfo req = GetFeatureInfo.create( "1.1.0", this.toString(), queryLayers, gmr, 893 "application/vnd.ogc.gml", request.getFeatureCount(), 894 request.getClickPoint(), request.getExceptions(), null, 895 request.getVendorSpecificParameters() ); 896 897 try { 898 LOG.logDebug( "cascaded GetFeatureInfo request: ", req.getRequestParameter() ); 899 } catch ( OGCWebServiceException e ) { 900 LOG.logError( e.getMessage(), e ); 901 } 902 903 return req; 904 } 905 906 /** 907 * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS 908 * implementation accessed by this class is able to return the result of a request by 909 * calling the write-method. 910 * 911 * @param result 912 * to a GetXXX request 913 * @return the response object 914 * @throws Exception 915 */ 916 private Object handleResponse( Object result ) 917 throws Exception { 918 Object[] response = null; 919 if ( result instanceof FeatureResult ) { 920 response = handleGetFeatureResponse( (FeatureResult) result ); 921 } else if ( result instanceof ResultCoverage ) { 922 response = handleGetCoverageResponse( (ResultCoverage) result ); 923 } else if ( result instanceof GetFeatureInfoResult ) { 924 925 response = handleGetFeatureInfoResult( (GetFeatureInfoResult) result ); 926 } else { 927 throw new Exception( Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ) ); 928 } 929 return response; 930 } 931 932 /** 933 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and 934 * a <tt>Theme</tt> from it 935 * 936 * @param response 937 * @return the response objects 938 * @throws Exception 939 */ 940 private Object[] handleGetFeatureResponse( FeatureResult response ) 941 throws Exception { 942 FeatureCollection fc = null; 943 944 Object o = response.getResponse(); 945 if ( o instanceof FeatureCollection ) { 946 fc = (FeatureCollection) o; 947 } else if ( o.getClass() == byte[].class ) { 948 Reader reader = new InputStreamReader( new ByteArrayInputStream( (byte[]) o ) ); 949 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument(); 950 doc.load( reader, XMLFragment.DEFAULT_URL ); 951 fc = doc.parse(); 952 } else { 953 throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) ); 954 } 955 Object[] ro = new Object[2]; 956 ro[0] = new Integer( index ); 957 ro[1] = fc; 958 return ro; 959 } 960 961 /** 962 * 963 * @param res 964 */ 965 private Object[] handleGetFeatureInfoResult( GetFeatureInfoResult res ) 966 throws Exception { 967 968 FeatureCollection fc = null; 969 StringReader sr = new StringReader( res.getFeatureInfo() ); 970 XMLFragment xml = new XMLFragment( sr, XMLFragment.DEFAULT_URL ); 971 972 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 973 LOG.logDebug( "GetFeature-response (before transformation): " + xml.getAsPrettyString() ); 974 } 975 976 URL url = ( (RemoteWMSDataSource) datasource ).getFeatureInfoTransform(); 977 if ( url != null ) { 978 // transform incoming GML/XML to a GML application schema 979 // that is understood by deegree 980 XSLTDocument xslt = new XSLTDocument(); 981 xslt.load( url ); 982 xml = xslt.transform( xml, null, null, null ); 983 } 984 GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument(); 985 doc.setRootElement( xml.getRootElement() ); 986 fc = doc.parse(); 987 Object[] ro = new Object[2]; 988 ro[0] = new Integer( index ); 989 ro[1] = fc; 990 return ro; 991 } 992 993 private Object[] handleGetCoverageResponse( ResultCoverage response ) 994 throws OGCWebServiceException { 995 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage(); 996 Object[] result = new Object[2]; 997 if ( gc != null ) { 998 BufferedImage bi = gc.getAsImage( -1, -1 ); 999 1000 float[][] data = new Image2RawData( bi ).parse(); 1001 1002 double scaleX = (double) data[0].length / (double) getMapRequest.getWidth(); 1003 double scaleY = (double) data.length / (double) getMapRequest.getHeight(); 1004 int pxSizeX = (int) Math.round( scaleX ); 1005 int pxSizeY = (int) Math.round( scaleY ); 1006 if ( pxSizeX == 0 ) { 1007 pxSizeX = 1; 1008 } 1009 if ( pxSizeY == 0 ) { 1010 pxSizeY = 1; 1011 } 1012 1013 LOG.logDebug( "Size of grid coverage is " + data[0].length + "x" + data.length + "." ); 1014 LOG.logDebug( "Returning an area of " + pxSizeX + "x" + pxSizeY + " pixels." ); 1015 1016 int ix = (int) ( request.getClickPoint().x * scaleX ) - pxSizeX / 2; 1017 int iy = (int) ( request.getClickPoint().y * scaleY ) - pxSizeY / 2; 1018 1019 // some checks to avoid areas that are not requestable 1020 if ( ix < 0 ) { 1021 ix = 0; 1022 } 1023 if ( iy < 0 ) { 1024 iy = 0; 1025 } 1026 if ( ix >= ( data[0].length - pxSizeX ) ) { 1027 ix = data[0].length - pxSizeX - 1; 1028 } 1029 if ( iy >= ( data.length - pxSizeY ) ) { 1030 iy = data.length - pxSizeY - 1; 1031 } 1032 1033 FeatureCollection fc = FeatureFactory.createFeatureCollection( gc.getCoverageOffering().getName(), 1034 pxSizeX * pxSizeY ); 1035 1036 PropertyType pt = null; 1037 try { 1038 pt = FeatureFactory.createPropertyType( VALUE, new QualifiedName( "xsd", "double", 1039 CommonNamespaces.XSNS ), 1, 1 ); 1040 } catch ( UnknownTypeException e ) { 1041 LOG.logError( "The xsd:double type is not known?!? Get a new deegree.jar!", e ); 1042 } 1043 FeatureType ft = FeatureFactory.createFeatureType( gc.getCoverageOffering().getName(), false, 1044 new PropertyType[] { pt } ); 1045 1046 for ( int x = ix; x < ix + pxSizeX; ++x ) { 1047 for ( int y = iy; y < iy + pxSizeY; ++y ) { 1048 FeatureProperty p = FeatureFactory.createFeatureProperty( VALUE, new Double( data[y][x] ) ); 1049 Feature f = FeatureFactory.createFeature( "ID_faked_for_" + x + "x" + y, ft, 1050 new FeatureProperty[] { p } ); 1051 fc.add( f ); 1052 } 1053 } 1054 1055 result[1] = fc; 1056 } else { 1057 throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE", 1058 datasource.getName() ) ); 1059 } 1060 1061 result[0] = new Integer( index ); 1062 return result; 1063 } 1064 1065 } 1066 1067 private class DoServiceTask implements Callable<Object> { 1068 1069 OGCWebService webService; 1070 1071 OGCWebServiceRequest request; 1072 1073 DoServiceTask( OGCWebService webService, OGCWebServiceRequest request ) { 1074 this.webService = webService; 1075 this.request = request; 1076 } 1077 1078 public Object call() 1079 throws Exception { 1080 return this.webService.doService( request ); 1081 } 1082 } 1083 1084 private class ServiceInvokerTask implements Callable<Object[]> { 1085 1086 ServiceInvoker invoker; 1087 1088 ServiceInvokerTask( ServiceInvoker invoker ) { 1089 this.invoker = invoker; 1090 } 1091 1092 public Object[] call() 1093 throws Exception { 1094 return (Object[]) this.invoker.run(); 1095 } 1096 } 1097 }