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