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