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