001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wmps/GetMapServiceInvokerForNL.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 53115 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 package org.deegree.ogcwebservices.wmps; 044 045 import java.awt.Color; 046 import java.awt.Graphics; 047 import java.awt.image.BufferedImage; 048 import java.io.StringReader; 049 import java.util.ArrayList; 050 051 import org.deegree.datatypes.QualifiedName; 052 import org.deegree.framework.log.ILogger; 053 import org.deegree.framework.log.LoggerFactory; 054 import org.deegree.framework.util.CharsetUtils; 055 import org.deegree.framework.util.IDGenerator; 056 import org.deegree.framework.xml.XMLTools; 057 import org.deegree.graphics.MapFactory; 058 import org.deegree.graphics.Theme; 059 import org.deegree.graphics.sld.UserStyle; 060 import org.deegree.model.coverage.grid.GridCoverage; 061 import org.deegree.model.coverage.grid.ImageGridCoverage; 062 import org.deegree.model.crs.CRSFactory; 063 import org.deegree.model.crs.GeoTransformer; 064 import org.deegree.model.feature.FeatureCollection; 065 import org.deegree.model.filterencoding.ComplexFilter; 066 import org.deegree.model.filterencoding.FeatureFilter; 067 import org.deegree.model.filterencoding.FeatureId; 068 import org.deegree.model.filterencoding.Filter; 069 import org.deegree.model.spatialschema.Envelope; 070 import org.deegree.model.spatialschema.GMLGeometryAdapter; 071 import org.deegree.ogcwebservices.InconsistentRequestException; 072 import org.deegree.ogcwebservices.OGCWebServiceException; 073 import org.deegree.ogcwebservices.OGCWebServiceRequest; 074 import org.deegree.ogcwebservices.wcs.WCSException; 075 import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage; 076 import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage; 077 import org.deegree.ogcwebservices.wfs.WFService; 078 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 079 import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType; 080 import org.deegree.ogcwebservices.wfs.operation.FeatureResult; 081 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 082 import org.deegree.ogcwebservices.wfs.operation.Query; 083 import org.deegree.ogcwebservices.wms.capabilities.Layer; 084 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 085 import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource; 086 import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource; 087 import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource; 088 import org.deegree.ogcwebservices.wms.operation.GetMap; 089 import org.deegree.ogcwebservices.wms.operation.GetMapResult; 090 import org.w3c.dom.Document; 091 092 /** 093 * This is a copy of the WMS package. 094 * 095 * @version $Revision: 9447 $ 096 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 097 * @author last edited by: $Author: rbezema $ 098 * 099 * @version 1.0. $Revision: 9447 $, $Date: 2008-01-08 12:01:24 +0100 (Di, 08 Jan 2008) $ 100 * 101 * @since 2.0 102 */ 103 /** 104 * Inner class for accessing the data of one named layer and creating <tt>DisplayElement</tt>s 105 * and a <tt>Thrme</tt> from it. The class extends <tt>Thread</tt> and implements the run 106 * method, so that a parallel data accessing from several layers is possible. 107 * 108 * @version $Revision: 9447 $ 109 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 110 */ 111 class GetMapServiceInvokerForNL extends Thread { 112 113 private static final ILogger LOG = LoggerFactory.getLogger( GetMapServiceInvokerForNL.class ); 114 115 private final DefaultGetMapHandler handler; 116 117 private Layer layer; 118 119 private UserStyle style; 120 121 private int index = 0; 122 123 private AbstractDataSource datasource; 124 125 /** 126 * Creates a new ServiceInvokerForNL object. 127 * 128 * @param handler 129 * @param lay 130 * @param source 131 * @param style 132 * @param index 133 * index of the requested layer 134 */ 135 GetMapServiceInvokerForNL( DefaultGetMapHandler handler, Layer lay, AbstractDataSource source, UserStyle style, 136 int index ) { 137 138 this.layer = lay; 139 this.handler = handler; 140 this.index = index; 141 this.style = style; 142 this.datasource = source; 143 } 144 145 /** 146 * overrides the run-method of the parent class <tt>Thread</tt> for enabling a multi-threaded 147 * access to the data. 148 */ 149 @Override 150 public void run() { 151 152 153 if ( this.datasource != null ) { 154 155 OGCWebServiceRequest request = null; 156 try { 157 int type = this.datasource.getType(); 158 switch ( type ) { 159 case AbstractDataSource.LOCALWFS: 160 case AbstractDataSource.REMOTEWFS: { 161 request = createGetFeatureRequest( (LocalWFSDataSource) this.datasource ); 162 break; 163 } 164 case AbstractDataSource.LOCALWCS: 165 case AbstractDataSource.REMOTEWCS: { 166 request = createGetCoverageRequest( this.datasource ); 167 break; 168 } 169 case AbstractDataSource.REMOTEWMS: { 170 String styleName = null; 171 172 if ( style != null ) { 173 styleName = style.getName(); 174 } 175 request = GetMap.createGetMapRequest( this.datasource, handler.request, styleName, layer.getName() ); 176 break; 177 } 178 } 179 } catch ( Exception e ) { 180 LOG.logError( e.getMessage(), e ); 181 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: " 182 + this.layer.getName(), 183 "Couldn't create query!" ); 184 this.handler.putTheme( this.index, exce ); 185 this.handler.increaseCounter(); 186 187 return; 188 } 189 190 try { 191 Object o = this.datasource.getOGCWebService().doService( request ); 192 handleResponse( o ); 193 } catch ( Exception e ) { 194 LOG.logError( "", e ); 195 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: " 196 + this.layer.getName(), 197 "Couldn't perform doService()!" 198 + e.getMessage() ); 199 this.handler.putTheme( this.index, exce ); 200 this.handler.increaseCounter(); 201 202 return; 203 } 204 } else { 205 // increase counter because there is no service to call so it 206 // is assumed that the request for the current layer if fullfilled 207 this.handler.increaseCounter(); 208 } 209 210 211 } 212 213 /** 214 * creates a getFeature request considering the getMap request and the filterconditions defined 215 * in the submitted <tt>DataSource</tt> object. The request will be encapsualted within a 216 * <tt>OGCWebServiceEvent</tt>. 217 * 218 * @param ds 219 * @return GetFeature event object containing a GetFeature request 220 * @throws Exception 221 */ 222 private GetFeature createGetFeatureRequest( LocalWFSDataSource ds ) 223 throws Exception { 224 225 226 Envelope bbox = this.handler.request.getBoundingBox(); 227 228 // transform request bounding box to the coordinate reference 229 // system the WFS holds the data if requesting CRS and WFS-Data 230 // crs are different 231 WFService wfs = (WFService) ds.getOGCWebService(); 232 // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities(); 233 WFSCapabilities capa = wfs.getCapabilities(); 234 235 QualifiedName gn = ds.getName(); 236 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn ); 237 238 if ( ft == null ) { 239 throw new OGCWebServiceException( "Feature Type:" + ds.getName() + " is not known by the WFS" ); 240 } 241 242 // enable different formatations of the crs encoding for GML geometries 243 String GML_SRS = "http://www.opengis.net/gml/srs/"; 244 String old_gml_srs = ft.getDefaultSRS().toASCIIString(); 245 String old_srs; 246 if ( old_gml_srs.startsWith( GML_SRS ) ) { 247 old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase(); 248 } else { 249 old_srs = old_gml_srs; 250 } 251 252 String new_srs = this.handler.request.getSrs(); 253 String new_gml_srs; 254 if ( old_gml_srs.startsWith( GML_SRS ) ) { 255 new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase(); 256 } else { 257 new_gml_srs = new_srs; 258 } 259 260 if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) { 261 GeoTransformer gt = new GeoTransformer( CRSFactory.create( old_srs ) ); 262 bbox = gt.transform( bbox, this.handler.reqCRS ); 263 } 264 265 // no filter condition has been defined 266 StringBuffer sb = new StringBuffer( 5000 ); 267 sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" ); 268 sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " ); 269 sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); 270 sb.append( "xmlns:gml='http://www.opengis.net/gml' " ); 271 sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' ); 272 sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " ); 273 sb.append( "service='WFS' version='1.1.0' " ); 274 if ( ds.getType() == AbstractDataSource.LOCALWFS ) { 275 sb.append( "outputFormat='FEATURECOLLECTION'>" ); 276 } else { 277 sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" ); 278 } 279 sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" ); 280 281 Query query = ds.getQuery(); 282 if ( query == null ) { 283 sb.append( "<ogc:Filter><ogc:BBOX>" ); 284 sb.append( "<PropertyName>" ); 285 sb.append( ds.getGeometryProperty().getPrefixedName() ); 286 sb.append( "</PropertyName>" ); 287 sb.append( GMLGeometryAdapter.exportAsBox( bbox ) ); 288 sb.append( "</ogc:BBOX>" ); 289 sb.append( "</ogc:Filter></Query></GetFeature>" ); 290 } else { 291 Filter filter = query.getFilter(); 292 sb.append( "<ogc:Filter>" ); 293 if ( filter instanceof ComplexFilter ) { 294 sb.append( "<ogc:And>" ); 295 sb.append( "<ogc:BBOX><PropertyName>" ).append( ds.getGeometryProperty().getPrefixedName() ); 296 sb.append( "</PropertyName>" ); 297 sb.append( GMLGeometryAdapter.exportAsBox( bbox ) ); 298 sb.append( "</ogc:BBOX>" ); 299 300 // add filter as defined in the layers datasource description 301 // to the filter expression 302 org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation(); 303 sb.append( op.toXML() ).append( "</ogc:And>" ); 304 } else { 305 ArrayList featureIds = ( (FeatureFilter) filter ).getFeatureIds(); 306 if ( featureIds.size() > 1 ) { 307 sb.append( "<ogc:And>" ); 308 } 309 for ( int i = 0; i < featureIds.size(); i++ ) { 310 FeatureId fid = (FeatureId) featureIds.get( i ); 311 sb.append( fid.toXML() ); 312 } 313 if ( featureIds.size() > 1 ) { 314 sb.append( "</ogc:And>" ); 315 } 316 } 317 sb.append( "</ogc:Filter></Query></GetFeature>" ); 318 } 319 320 // create dom representation of the request 321 Document doc = XMLTools.parse( new StringReader( sb.toString() ) ); 322 323 // create OGCWebServiceEvent object 324 IDGenerator idg = IDGenerator.getInstance(); 325 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() ); 326 327 328 return gfr; 329 } 330 331 /** 332 * creates a getCoverage request considering the getMap request and the filterconditions defined 333 * in the submitted <tt>DataSource</tt> object The request will be encapsualted within a 334 * <tt>OGCWebServiceEvent</tt>. 335 * 336 * @param ds 337 * @return GetCoverage event object containing a GetCoverage request 338 * @throws InconsistentRequestException 339 */ 340 private GetCoverage createGetCoverageRequest( AbstractDataSource ds ) 341 throws InconsistentRequestException { 342 343 344 Envelope bbox = this.handler.request.getBoundingBox(); 345 346 GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest(); 347 348 String crs = this.handler.request.getSrs(); 349 if ( gcr != null && gcr.getDomainSubset().getRequestSRS() != null ) { 350 crs = gcr.getDomainSubset().getRequestSRS().getCode(); 351 } 352 String format = this.handler.request.getFormat(); 353 int pos = format.indexOf( '/' ); 354 if ( pos > -1 ) 355 format = format.substring( pos + 1, format.length() ); 356 if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) { 357 format = gcr.getOutput().getFormat().getCode(); 358 } 359 if ( format.indexOf( "svg" ) > -1 ) { 360 format = "tiff"; 361 } 362 363 String version = "1.0.0"; 364 if ( gcr != null && gcr.getVersion() != null ) { 365 version = gcr.getVersion(); 366 } 367 String lay = ds.getName().getPrefixedName(); 368 if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) { 369 lay = gcr.getSourceCoverage(); 370 } 371 String ipm = null; 372 if ( gcr != null && gcr.getInterpolationMethod() != null ) { 373 ipm = gcr.getInterpolationMethod().value; 374 } 375 376 // TODO 377 // handle rangesets e.g. time and elevation 378 StringBuffer sb = new StringBuffer( 1000 ); 379 sb.append( "service=WCS&request=GetCoverage" ); 380 sb.append( "&version=" ).append( version ); 381 sb.append( "&COVERAGE=" ).append( lay ); 382 sb.append( "&CRS=" ).append( crs ); 383 sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' ).append( bbox.getMin().getY() ).append( ',' ).append( 384 bbox.getMax().getX() ).append( 385 ',' ).append( 386 bbox.getMax().getY() ); 387 sb.append( "&WIDTH=" ).append( this.handler.request.getWidth() ); 388 sb.append( "&HEIGHT=" ).append( this.handler.request.getHeight() ); 389 sb.append( "&FORMAT=" ).append( format ); 390 sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm ); 391 try { 392 IDGenerator idg = IDGenerator.getInstance(); 393 gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() ); 394 } catch ( WCSException e ) { 395 throw new InconsistentRequestException( e.getMessage() ); 396 } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) { 397 throw new InconsistentRequestException( e.getMessage() ); 398 } 399 400 401 return gcr; 402 403 } 404 405 /** 406 * The method implements the <tt>OGCWebServiceClient</tt> interface. So a deegree OWS 407 * implementation accessed by this class is able to return the result of a request by calling 408 * the write-method. 409 * 410 * @param result 411 * to a GetXXX request 412 */ 413 private void handleResponse( Object result ) { 414 415 try { 416 if ( result instanceof ResultCoverage ) { 417 handleGetCoverageResponse( (ResultCoverage) result ); 418 } else if ( result instanceof FeatureResult ) { 419 handleGetFeatureResponse( (FeatureResult) result ); 420 } else if ( result instanceof GetMapResult ) { 421 handleGetMapResponse( (GetMapResult) result ); 422 } else { 423 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: " 424 + this.layer.getName(), 425 "unknown response format!" ); 426 this.handler.putTheme( this.index, exce ); 427 } 428 } catch ( Exception e ) { 429 LOG.logError( "-", e ); 430 OGCWebServiceException exce = new OGCWebServiceException( "ServiceInvokerForNL: " + this.layer.getName(), 431 e.toString() ); 432 this.handler.putTheme( this.index, exce ); 433 } 434 // increase counter to indicate that one more layers requesting is 435 // completed 436 this.handler.increaseCounter(); 437 } 438 439 /** 440 * replaces all pixels within the passed image having a color that is defined to be transparent 441 * within their datasource with a transparent color. 442 * 443 * @param img 444 * @return BufferedImage 445 */ 446 private BufferedImage setTransparentColors( BufferedImage img ) { 447 448 449 Color[] colors = null; 450 if ( this.datasource.getType() == AbstractDataSource.LOCALWCS ) { 451 LocalWCSDataSource ds = (LocalWCSDataSource) this.datasource; 452 colors = ds.getTransparentColors(); 453 } else { 454 RemoteWMSDataSource ds = (RemoteWMSDataSource) this.datasource; 455 colors = ds.getTransparentColors(); 456 } 457 458 if ( colors != null && colors.length > 0 ) { 459 460 int[] clrs = new int[colors.length]; 461 for ( int i = 0; i < clrs.length; i++ ) { 462 clrs[i] = colors[i].getRGB(); 463 } 464 465 if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) { 466 // if the incoming image does not allow transparency 467 // it must be copyed to a image of ARGB type 468 BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB ); 469 Graphics g = tmp.getGraphics(); 470 g.drawImage( img, 0, 0, null ); 471 g.dispose(); 472 img = tmp; 473 } 474 475 // TODO 476 // should be replaced by a JAI operation 477 int w = img.getWidth(); 478 int h = img.getHeight(); 479 for ( int i = 0; i < w; i++ ) { 480 for ( int j = 0; j < h; j++ ) { 481 int col = img.getRGB( i, j ); 482 if ( shouldBeTransparent( clrs, col ) ) { 483 img.setRGB( i, j, 0x00FFFFFF ); 484 } 485 } 486 } 487 } 488 489 return img; 490 } 491 492 /** 493 * Should be transparent. 494 * 495 * @param colors 496 * @param color 497 * @return boolean 498 */ 499 private boolean shouldBeTransparent( int[] colors, int color ) { 500 for ( int i = 0; i < colors.length; i++ ) { 501 if ( colors[i] == color ) { 502 return true; 503 } 504 } 505 return false; 506 } 507 508 /** 509 * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> 510 * and a <tt>Theme</tt> from it 511 * 512 * @param response 513 * @throws Exception 514 */ 515 private void handleGetMapResponse( GetMapResult response ) 516 throws Exception { 517 518 519 BufferedImage bi = (BufferedImage) response.getMap(); 520 bi = setTransparentColors( bi ); 521 GridCoverage gc = new ImageGridCoverage( null, this.handler.request.getBoundingBox(), bi ); 522 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( this.layer.getName(), gc ); 523 Theme theme = MapFactory.createTheme( this.datasource.getName().getPrefixedName(), rl ); 524 this.handler.putTheme( this.index, theme ); 525 526 } 527 528 /** 529 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a 530 * <tt>Theme</tt> from it 531 * 532 * @param response 533 * @throws Exception 534 */ 535 private void handleGetFeatureResponse( FeatureResult response ) 536 throws Exception { 537 538 539 FeatureCollection fc = null; 540 541 Object o = response.getResponse(); 542 543 if ( o instanceof FeatureCollection ) { 544 fc = (FeatureCollection) o; 545 } else { 546 throw new Exception( "unknown data format at a GetFeature response" ); 547 } 548 org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( this.layer.getName(), this.handler.reqCRS, fc ); 549 550 this.handler.putTheme( this.index, MapFactory.createTheme( this.datasource.getName().getPrefixedName(), fl, 551 new UserStyle[] { this.style } ) ); 552 553 554 } 555 556 /** 557 * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a 558 * <tt>Theme</tt> from it 559 * 560 * @param response 561 * @throws Exception 562 */ 563 private void handleGetCoverageResponse( ResultCoverage response ) 564 throws Exception { 565 566 567 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage(); 568 BufferedImage bi = gc.getAsImage( -1, -1 ); 569 570 bi = setTransparentColors( bi ); 571 572 gc = new ImageGridCoverage( null, this.handler.request.getBoundingBox(), bi ); 573 574 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( this.layer.getName(), gc ); 575 576 this.handler.putTheme( this.index, MapFactory.createTheme( this.datasource.getName().getPrefixedName(), rl ) ); 577 578 } 579 580 }