001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/ogcwebservices/wms/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.wms; 037 038 import static org.deegree.graphics.sld.StyleUtils.extractRequiredProperties; 039 import static org.deegree.i18n.Messages.get; 040 import static org.deegree.model.spatialschema.GMLGeometryAdapter.exportAsBox; 041 import static org.deegree.ogcbase.ExceptionCode.INVALIDDIMENSIONVALUE; 042 import static org.deegree.ogcbase.ExceptionCode.MISSINGDIMENSIONVALUE; 043 import static org.deegree.ogcbase.PropertyPathFactory.createPropertyPath; 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.net.URI; 050 import java.util.ArrayList; 051 import java.util.Iterator; 052 import java.util.LinkedList; 053 import java.util.List; 054 import java.util.Map; 055 import java.util.concurrent.Callable; 056 import java.util.concurrent.CancellationException; 057 058 import org.deegree.datatypes.QualifiedName; 059 import org.deegree.datatypes.Types; 060 import org.deegree.framework.concurrent.DoDatabaseQueryTask; 061 import org.deegree.framework.concurrent.DoExternalAccessTask; 062 import org.deegree.framework.concurrent.DoServiceTask; 063 import org.deegree.framework.concurrent.Executor; 064 import org.deegree.framework.log.ILogger; 065 import org.deegree.framework.log.LoggerFactory; 066 import org.deegree.framework.util.CharsetUtils; 067 import org.deegree.framework.util.IDGenerator; 068 import org.deegree.framework.xml.XMLFragment; 069 import org.deegree.framework.xml.XMLTools; 070 import org.deegree.graphics.MapFactory; 071 import org.deegree.graphics.Theme; 072 import org.deegree.graphics.sld.FeatureTypeConstraint; 073 import org.deegree.graphics.sld.LayerFeatureConstraints; 074 import org.deegree.graphics.sld.NamedLayer; 075 import org.deegree.graphics.sld.UserStyle; 076 import org.deegree.i18n.Messages; 077 import org.deegree.model.coverage.grid.GridCoverage; 078 import org.deegree.model.coverage.grid.ImageGridCoverage; 079 import org.deegree.model.coverage.grid.WorldFile; 080 import org.deegree.model.crs.CRSFactory; 081 import org.deegree.model.crs.CRSTransformationException; 082 import org.deegree.model.crs.CoordinateSystem; 083 import org.deegree.model.crs.GeoTransformer; 084 import org.deegree.model.crs.UnknownCRSException; 085 import org.deegree.model.feature.Feature; 086 import org.deegree.model.feature.FeatureCollection; 087 import org.deegree.model.feature.FeatureProperty; 088 import org.deegree.model.feature.schema.FeatureType; 089 import org.deegree.model.feature.schema.PropertyType; 090 import org.deegree.model.filterencoding.ComplexFilter; 091 import org.deegree.model.filterencoding.FeatureFilter; 092 import org.deegree.model.filterencoding.FeatureId; 093 import org.deegree.model.filterencoding.Filter; 094 import org.deegree.model.spatialschema.Envelope; 095 import org.deegree.model.spatialschema.Geometry; 096 import org.deegree.model.spatialschema.GeometryException; 097 import org.deegree.model.spatialschema.GeometryFactory; 098 import org.deegree.model.spatialschema.WKTAdapter; 099 import org.deegree.ogcbase.PropertyPath; 100 import org.deegree.ogcbase.PropertyPathFactory; 101 import org.deegree.ogcbase.SortProperty; 102 import org.deegree.ogcwebservices.InconsistentRequestException; 103 import org.deegree.ogcwebservices.InvalidParameterValueException; 104 import org.deegree.ogcwebservices.OGCWebService; 105 import org.deegree.ogcwebservices.OGCWebServiceException; 106 import org.deegree.ogcwebservices.OGCWebServiceRequest; 107 import org.deegree.ogcwebservices.wcs.WCSException; 108 import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage; 109 import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage; 110 import org.deegree.ogcwebservices.wfs.RemoteWFService; 111 import org.deegree.ogcwebservices.wfs.WFService; 112 import org.deegree.ogcwebservices.wfs.capabilities.WFSCapabilities; 113 import org.deegree.ogcwebservices.wfs.capabilities.WFSFeatureType; 114 import org.deegree.ogcwebservices.wfs.operation.FeatureResult; 115 import org.deegree.ogcwebservices.wfs.operation.GetFeature; 116 import org.deegree.ogcwebservices.wfs.operation.Query; 117 import org.deegree.ogcwebservices.wms.capabilities.Dimension; 118 import org.deegree.ogcwebservices.wms.capabilities.Layer; 119 import org.deegree.ogcwebservices.wms.configuration.AbstractDataSource; 120 import org.deegree.ogcwebservices.wms.configuration.DatabaseDataSource; 121 import org.deegree.ogcwebservices.wms.configuration.ExternalDataAccessDataSource; 122 import org.deegree.ogcwebservices.wms.configuration.LocalWCSDataSource; 123 import org.deegree.ogcwebservices.wms.configuration.LocalWFSDataSource; 124 import org.deegree.ogcwebservices.wms.configuration.RemoteWCSDataSource; 125 import org.deegree.ogcwebservices.wms.configuration.RemoteWMSDataSource; 126 import org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess; 127 import org.deegree.ogcwebservices.wms.operation.DimensionValues; 128 import org.deegree.ogcwebservices.wms.operation.GetMap; 129 import org.deegree.ogcwebservices.wms.operation.GetMapResult; 130 import org.deegree.ogcwebservices.wms.operation.DimensionValues.DimensionValue; 131 import org.w3c.dom.Document; 132 133 /** 134 * Class for accessing the data of one layers datasource and creating a <tt>Theme</tt> from it. 135 * 136 * @version $Revision: 22342 $ 137 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 138 * @author last edited by: $Author: aschmitz $ 139 * 140 * @version 1.0. $Revision: 22342 $, $Date: 2010-02-02 16:58:18 +0100 (Di, 02 Feb 2010) $ 141 * 142 * @since 2.0 143 */ 144 public class GetMapServiceInvokerForNL extends GetMapServiceInvoker implements Callable<Object> { 145 146 private static final ILogger LOG = LoggerFactory.getLogger( GetMapServiceInvokerForNL.class ); 147 148 private final GetMap request; 149 150 private NamedLayer layer = null; 151 152 private UserStyle style = null; 153 154 private AbstractDataSource datasource = null; 155 156 /** 157 * Creates a new ServiceInvokerForNL object. 158 * 159 * @param handler 160 * @param layer 161 * @param datasource 162 * @param style 163 * @param scale 164 */ 165 GetMapServiceInvokerForNL( DefaultGetMapHandler handler, NamedLayer layer, AbstractDataSource datasource, 166 UserStyle style, double scale ) { 167 168 super( handler, scale ); 169 170 this.layer = layer; 171 this.request = handler.getRequest(); 172 this.style = style; 173 this.datasource = datasource; 174 } 175 176 /** 177 * @return the callable actually fetching the data 178 * @throws Exception 179 */ 180 public Callable<Object> getWrappedCallable() 181 throws Exception { 182 int type = datasource.getType(); 183 switch ( type ) { 184 case AbstractDataSource.LOCALWFS: 185 case AbstractDataSource.REMOTEWFS: { 186 OGCWebServiceRequest request = createGetFeatureRequest( (LocalWFSDataSource) datasource ); 187 return new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 188 } 189 case AbstractDataSource.LOCALWCS: 190 case AbstractDataSource.REMOTEWCS: { 191 OGCWebServiceRequest request = createGetCoverageRequest( datasource, this.request ); 192 return new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 193 } 194 case AbstractDataSource.REMOTEWMS: { 195 String styleName = null; 196 197 if ( style != null ) { 198 styleName = style.getName(); 199 } 200 201 OGCWebServiceRequest request = GetMap.createGetMapRequest( datasource, handler.getRequest(), styleName, 202 layer.getName() ); 203 LOG.logDebug( "GetMap request: " + request.toString() ); 204 return new DoServiceTask<Object>( datasource.getOGCWebService(), request ); 205 } 206 case AbstractDataSource.DATABASE: { 207 CoordinateSystem crs = CRSFactory.create( this.request.getSrs() ); 208 Envelope env = this.request.getBoundingBox(); 209 env = GeometryFactory.createEnvelope( env.getMin(), env.getMax(), crs ); 210 211 // set dim default values TODO do so in general? 212 for ( Dimension dim : handler.getConfiguration().getLayer( layer.getName() ).getDimension() ) { 213 if ( dim.getDefaultValue() != null ) { 214 if ( dim.getName().equals( "time" ) && request.getDimTime() == null ) { 215 request.setDimTime( new DimensionValues( dim.getDefaultValue() ) ); 216 } else if ( dim.getName().equals( "elevation" ) && request.getDimElev() == null ) { 217 request.setDimElev( new DimensionValues( dim.getDefaultValue() ) ); 218 } 219 } 220 } 221 222 if ( handler.sqls != null ) { 223 return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env, 224 handler.sqls.get( layer.getName() ), datasource.getDimProps(), request ); 225 } 226 return new DoDatabaseQueryTask( (DatabaseDataSource) datasource, env, null, datasource.getDimProps(), 227 request ); 228 } 229 case AbstractDataSource.EXTERNALDATAACCESS: { 230 ExternalDataAccess eda = ( (ExternalDataAccessDataSource) datasource ).getExternalDataAccess(); 231 return new DoExternalAccessTask( eda, request ); 232 } 233 default: 234 return null; 235 } 236 } 237 238 public Object call() 239 throws OGCWebServiceException { 240 241 Object response = null; 242 if ( datasource != null ) { 243 Callable<Object> task = null; 244 try { 245 task = getWrappedCallable(); 246 } catch ( OGCWebServiceException e ) { 247 throw e; 248 } catch ( Exception e ) { 249 LOG.logError( "Exception during fetching data for some data source", e ); 250 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), 251 Messages.getMessage( "WMS_ERRORQUERYCREATE", 252 e ) ); 253 // exception can't be re-thrown because responsible GetMapHandler 254 // must collect all responses of all data sources 255 response = exce; 256 } 257 258 try { 259 // start reading data with a limited time frame. The time limit 260 // read from the data source must be multiplied by 1000 because 261 // the method expects milliseconds as time limit 262 Executor executor = Executor.getInstance(); 263 Object o = executor.performSynchronously( task, datasource.getRequestTimeLimit() * 1000 ); 264 response = handleResponse( o ); 265 } catch ( CancellationException e ) { 266 // exception can't be re-thrown because responsible GetMapHandler 267 // must collect all responses of all data sources 268 String s = Messages.getMessage( "WMS_TIMEOUTDATASOURCE", new Integer( datasource.getRequestTimeLimit() ) ); 269 LOG.logError( s, e ); 270 if ( datasource.isFailOnException() ) { 271 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 272 response = exce; 273 } else { 274 response = null; 275 } 276 } catch ( WMSExceptionFromWCS e ) { 277 if ( datasource.isFailOnException() ) { 278 response = e; 279 } 280 } catch ( Throwable e ) { 281 // exception can't be re-thrown because responsible GetMapHandler 282 // must collect all responses of all data sources 283 String s = Messages.getMessage( "WMS_ERRORDOSERVICE", e.getMessage() ); 284 LOG.logError( s, e ); 285 if ( datasource.isFailOnException() ) { 286 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 287 response = exce; 288 } else { 289 response = null; 290 } 291 } 292 } 293 294 LOG.logDebug( "Layer " + layer.getName() + " returned." ); 295 296 return response; 297 } 298 299 /** 300 * creates a getFeature request considering the getMap request and the filterconditions defined in the submitted 301 * <tt>DataSource</tt> object. The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>. 302 * 303 * @param ds 304 * @return GetFeature request object 305 * @throws Exception 306 */ 307 private GetFeature createGetFeatureRequest( LocalWFSDataSource ds ) 308 throws Exception { 309 310 Envelope bbox = transformBBOX( ds ); 311 312 LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(), sortProperties = new LinkedList<StringBuffer>(); 313 314 List<PropertyPath> pp = null; 315 if ( style != null ) { 316 List<UserStyle> styleList = new ArrayList<UserStyle>(); 317 styleList.add( style ); 318 pp = extractRequiredProperties( styleList, scaleDen ); 319 } else { 320 pp = new ArrayList<PropertyPath>(); 321 } 322 PropertyPath geomPP = PropertyPathFactory.createPropertyPath( ds.getGeometryProperty() ); 323 if ( !pp.contains( geomPP ) ) { 324 pp.add( geomPP ); 325 } 326 327 // handling of vendor specific parameters "filterproperty" and "filtervalue" 328 String filterProperty = request.getVendorSpecificParameter( "FILTERPROPERTY" ); 329 String filterValue = request.getVendorSpecificParameter( "FILTERVALUE" ); 330 331 boolean useCustomFilter = handler.getConfiguration().getDeegreeParams().getFiltersAllowed(); 332 useCustomFilter = useCustomFilter && filterProperty != null && filterValue != null; 333 334 if ( useCustomFilter ) { 335 LOG.logDebug( "Using custom filter on " + filterProperty + " = " + filterValue ); 336 PropertyPath path = createPropertyPath( new QualifiedName( filterProperty ) ); 337 if ( !pp.contains( path ) ) { 338 pp.add( path ); 339 } 340 } 341 342 LOG.logDebug( "required properties: ", pp ); 343 Map<String, URI> namesp = extractNameSpaceDef( pp ); 344 345 // no filter condition has been defined 346 StringBuffer sb = new StringBuffer( 5000 ); 347 sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" ); 348 sb.append( "<GetFeature xmlns='http://www.opengis.net/wfs' " ); 349 sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " ); 350 sb.append( "xmlns:gml='http://www.opengis.net/gml' " ); 351 sb.append( "xmlns:" ).append( ds.getName().getPrefix() ).append( '=' ); 352 sb.append( "'" ).append( ds.getName().getNamespace() ).append( "' " ); 353 Iterator<String> iter = namesp.keySet().iterator(); 354 while ( iter.hasNext() ) { 355 String pre = iter.next(); 356 URI nsp = namesp.get( pre ); 357 if ( !pre.equals( "xmlns" ) && !pre.equals( ds.getName().getPrefix() ) ) { 358 sb.append( "xmlns:" ).append( pre ).append( "='" ); 359 sb.append( nsp.toASCIIString() ).append( "' " ); 360 } 361 } 362 363 sb.append( "service='WFS' version='1.1.0' " ); 364 if ( ds.getType() == AbstractDataSource.LOCALWFS ) { 365 sb.append( "outputFormat='FEATURECOLLECTION'>" ); 366 } else { 367 sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" ); 368 } 369 sb.append( "<Query typeName='" + ds.getName().getPrefixedName() + "'>" ); 370 371 // please note that this code CANNOT BE UNCOMMENTED as it severely affects performance in many cases! 372 for ( int j = 0; j < pp.size(); j++ ) { 373 if ( !pp.get( j ).getAsString().endsWith( "$SCALE" ) ) { 374 sb.append( "<PropertyName>" ).append( pp.get( j ).getAsString() ); 375 sb.append( "</PropertyName>" ); 376 } 377 } 378 379 // add filters to list 380 // BBOX filter 381 StringBuffer sb2 = new StringBuffer( 512 ); 382 sb2.append( "<ogc:BBOX>" ); 383 sb2.append( "<PropertyName>" ); 384 sb2.append( ds.getGeometryProperty().getPrefixedName() ); 385 sb2.append( "</PropertyName>" ); 386 sb2.append( exportAsBox( bbox ) ); 387 sb2.append( "</ogc:BBOX>" ); 388 filters.add( sb2 ); 389 // custom filter for property value 390 if ( useCustomFilter ) { 391 sb2 = new StringBuffer( 512 ); 392 appendCustomFilter( sb2, filterProperty, filterValue ); 393 filters.add( sb2 ); 394 } 395 // filters of query 396 Query query = ds.getQuery(); 397 if ( query != null ) { 398 Filter filter = query.getFilter(); 399 filters.addAll( extractFilters( filter ) ); 400 SortProperty[] sps = query.getSortProperties(); 401 if ( sps != null ) { 402 for ( SortProperty sp : sps ) { 403 sortProperties.add( extractSortProperty( sp ) ); 404 } 405 } 406 } 407 // filters from SLD 408 if ( layer != null ) { 409 LayerFeatureConstraints lfc = layer.getLayerFeatureConstraints(); 410 if ( lfc != null ) { 411 FeatureTypeConstraint[] fcs = lfc.getFeatureTypeConstraint(); 412 if ( fcs != null ) { 413 for ( FeatureTypeConstraint fc : fcs ) { 414 filters.addAll( extractFilters( fc.getFilter() ) ); 415 } 416 } 417 } 418 } 419 420 // filters from the dimensions 421 Layer lay = handler.getConfiguration().getLayer( layer.getName() ); 422 Map<String, String> dimProps = ds.getDimProps(); 423 if ( lay.getDimension() != null && dimProps != null ) { 424 for ( Dimension dim : lay.getDimension() ) { 425 if ( dim.getName().equals( "time" ) ) { 426 filters.add( handleDimension( request.getDimTime(), dim, dimProps.get( "time" ) ) ); 427 } 428 if ( dim.getName().equals( "elevation" ) ) { 429 filters.add( handleDimension( request.getDimElev(), dim, dimProps.get( "elevation" ) ) ); 430 } 431 } 432 } 433 434 // actually append the filters 435 sb.append( "<ogc:Filter>" ); 436 if ( filters.size() > 1 ) { 437 sb.append( "<ogc:And>" ); 438 } 439 for ( StringBuffer s : filters ) { 440 sb.append( s ); 441 } 442 if ( filters.size() > 1 ) { 443 sb.append( "</ogc:And>" ); 444 } 445 sb.append( "</ogc:Filter>" ); 446 if ( sortProperties.size() > 0 ) { 447 sb.append( "<ogc:SortBy>" ); 448 for ( StringBuffer s : sortProperties ) { 449 sb.append( s ); 450 } 451 sb.append( "</ogc:SortBy>" ); 452 } 453 sb.append( "</Query></GetFeature>" ); 454 455 // create dom representation of the request 456 Document doc = XMLTools.parse( new StringReader( sb.toString() ) ); 457 458 if ( LOG.isDebug() ) { 459 LOG.logDebug( "GetFeature request: " 460 + new XMLFragment( doc, "http://www.systemid.org" ).getAsPrettyString() ); 461 } 462 463 // create OGCWebServiceEvent object 464 IDGenerator idg = IDGenerator.getInstance(); 465 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() ); 466 467 return gfr; 468 } 469 470 private StringBuffer handleDimension( DimensionValues values, Dimension dim, String dimProp ) 471 throws OGCWebServiceException { 472 if ( values == null && dim.getDefaultValue() != null ) { 473 values = new DimensionValues( dim.getDefaultValue() ); 474 request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" + dim.getDefaultValue() + " " 475 + dim.getUnits() ); 476 } 477 478 if ( values == null ) { 479 throw new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", dim.getName() ), 480 MISSINGDIMENSIONVALUE ); 481 } 482 if ( !dim.isMultipleValues() && values.hasMultipleValues() ) { 483 throw new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", dim.getName() ), 484 INVALIDDIMENSIONVALUE ); 485 } 486 487 String singleValue = values.values.peek().value; 488 if ( values.values.size() == 1 && singleValue != null ) { 489 DimensionValues origValues = new DimensionValues( dim.getValues() ); 490 if ( !origValues.includesValue( singleValue ) ) { 491 if ( dim.isNearestValue() ) { 492 String nearestValue = origValues.getNearestValue( singleValue ); 493 values.values.peek().value = nearestValue; 494 request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" + nearestValue + " " 495 + dim.getUnits() ); 496 } 497 } 498 } 499 500 LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(); 501 502 for ( DimensionValue val : values.values ) { 503 StringBuffer sb = new StringBuffer( 512 ); 504 if ( val.value != null ) { 505 sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ).append( dimProp ); 506 sb.append( "</ogc:PropertyName><ogc:Literal>" ).append( val.value ); 507 sb.append( "</ogc:Literal></ogc:PropertyIsEqualTo>" ); 508 } else { 509 sb.append( "<ogc:PropertyIsBetween><ogc:PropertyName>" ).append( dimProp ); 510 sb.append( "</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>" ).append( val.low ); 511 sb.append( "</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>" ).append( val.high ); 512 sb.append( "</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween>" ); 513 } 514 filters.add( sb ); 515 } 516 517 if ( filters.size() == 1 ) { 518 return filters.poll(); 519 } 520 521 StringBuffer sb = new StringBuffer( 512 * filters.size() ); 522 sb.append( "<ogc:Or>" ); 523 while ( filters.size() > 0 ) { 524 sb.append( filters.poll() ); 525 } 526 sb.append( "</ogc:Or>" ); 527 return sb; 528 } 529 530 private static StringBuffer extractSortProperty( SortProperty sp ) { 531 StringBuffer sb = new StringBuffer(); 532 sb.append( "<ogc:SortProperty>" ); 533 sb.append( "<ogc:PropertyName>" ); 534 sb.append( sp.getSortProperty().toString() ); 535 sb.append( "</ogc:PropertyName>" ); 536 sb.append( "<ogc:SortOrder>" ); 537 sb.append( sp.getSortOrder() ? "ASC" : "DESC" ); 538 sb.append( "</ogc:SortOrder>" ); 539 sb.append( "</ogc:SortProperty>" ); 540 541 return sb; 542 } 543 544 private static LinkedList<StringBuffer> extractFilters( Filter filter ) { 545 LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(); 546 547 if ( filter instanceof ComplexFilter ) { 548 filters.add( ( ( (ComplexFilter) filter ).getOperation() ).toXML() ); 549 } 550 if ( filter instanceof FeatureFilter ) { 551 ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds(); 552 for ( int i = 0; i < featureIds.size(); i++ ) { 553 FeatureId fid = featureIds.get( i ); 554 filters.add( fid.toXML() ); 555 } 556 } 557 558 return filters; 559 } 560 561 private static void appendCustomFilter( StringBuffer sb, String name, String value ) { 562 String[] vals = value.split( "," ); 563 if ( vals.length > 1 ) { 564 sb.append( "<ogc:Or>" ); 565 } 566 for ( String val : vals ) { 567 sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ); 568 sb.append( name ).append( "</ogc:PropertyName><ogc:Literal>" ); 569 sb.append( val ).append( "</ogc:Literal></ogc:PropertyIsEqualTo>" ); 570 } 571 if ( vals.length > 1 ) { 572 sb.append( "</ogc:Or>" ); 573 } 574 } 575 576 /** 577 * transforms the requested BBOX into the DefaultSRS of the assigned feature type 578 * 579 * @param ds 580 * @return the envelope 581 * @throws OGCWebServiceException 582 * @throws CRSTransformationException 583 * @throws UnknownCRSException 584 */ 585 private Envelope transformBBOX( LocalWFSDataSource ds ) 586 throws OGCWebServiceException, CRSTransformationException, UnknownCRSException { 587 Envelope bbox = request.getBoundingBox(); 588 // transform request bounding box to the coordinate reference 589 // system the WFS holds the data if requesting CRS and WFS-Data 590 // crs are different 591 OGCWebService service = ds.getOGCWebService(); 592 WFSCapabilities capa; 593 if ( service instanceof RemoteWFService ) { 594 RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService(); 595 capa = wfs.getWFSCapabilities(); 596 } else { 597 WFService wfs = (WFService) service; 598 capa = wfs.getCapabilities(); 599 } 600 // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities(); 601 QualifiedName gn = ds.getName(); 602 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn ); 603 604 if ( ft == null ) { 605 throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) ); 606 } 607 608 // enable different formatations of the crs encoding for GML geometries 609 String GML_SRS = "http://www.opengis.net/gml/srs/"; 610 String old_gml_srs = ft.getDefaultSRS().toASCIIString(); 611 String old_srs; 612 if ( old_gml_srs.startsWith( GML_SRS ) ) { 613 old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase(); 614 } else { 615 old_srs = old_gml_srs; 616 } 617 618 String new_srs = request.getSrs(); 619 String new_gml_srs; 620 if ( old_gml_srs.startsWith( GML_SRS ) ) { 621 new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase(); 622 } else { 623 new_gml_srs = new_srs; 624 } 625 626 if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) { 627 GeoTransformer transformer = new GeoTransformer( CRSFactory.create( old_srs ) ); 628 bbox = transformer.transform( bbox, this.handler.getRequestCRS() ); 629 } 630 return bbox; 631 } 632 633 /** 634 * creates a getCoverage request considering the getMap request and the filterconditions defined in the submitted 635 * <tt>DataSource</tt> object The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>. 636 * 637 * @param ds 638 * the datasource 639 * @param request 640 * the GetMap operation 641 * @return GetCoverage request object 642 * @throws InconsistentRequestException 643 */ 644 protected static GetCoverage createGetCoverageRequest( AbstractDataSource ds, GetMap request ) 645 throws InconsistentRequestException { 646 647 Envelope bbox = request.getBoundingBox(); 648 649 double xres = bbox.getWidth() / request.getWidth(); 650 double yres = bbox.getHeight() / request.getHeight(); 651 652 WorldFile tmpWorldFile = new WorldFile( xres, yres, 0.0, 0.0, bbox, WorldFile.TYPE.OUTER ); 653 bbox = tmpWorldFile.getEnvelope( WorldFile.TYPE.CENTER ); 654 655 GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest(); 656 657 String crs = request.getSrs(); 658 // if (gcr != null && gcr.getDomainSubset().getRequestSRS() != null) { 659 // crs = gcr.getDomainSubset().getRequestSRS().getCode(); 660 // } 661 String format = request.getFormat(); 662 int pos = format.indexOf( '/' ); 663 if ( pos > -1 ) 664 format = format.substring( pos + 1, format.length() ); 665 if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) { 666 format = gcr.getOutput().getFormat().getCode(); 667 } 668 if ( format.indexOf( "svg" ) > -1 ) { 669 format = "tiff"; 670 } 671 if ( format.startsWith( "png" ) ) { 672 format = "png"; 673 } 674 675 String version = "1.0.0"; 676 if ( gcr != null && gcr.getVersion() != null ) { 677 version = gcr.getVersion(); 678 } 679 String lay = ds.getName().getPrefixedName(); 680 if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) { 681 lay = gcr.getSourceCoverage(); 682 } 683 String ipm = null; 684 if ( gcr != null && gcr.getInterpolationMethod() != null ) { 685 ipm = gcr.getInterpolationMethod().value; 686 } 687 688 // TODO 689 // handle range sets e.g. time 690 // note that elevation is now handled after the results are in (see the handleGetCoverageResponse method below) 691 StringBuffer sb = new StringBuffer( 1000 ); 692 sb.append( "service=WCS&request=GetCoverage" ); 693 sb.append( "&version=" ).append( version ); 694 sb.append( "&COVERAGE=" ).append( lay ); 695 sb.append( "&crs=" ).append( crs ); 696 sb.append( "&response_crs=" ).append( crs ); 697 sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' ); 698 sb.append( bbox.getMin().getY() ).append( ',' ).append( bbox.getMax().getX() ); 699 sb.append( ',' ).append( bbox.getMax().getY() ); 700 sb.append( "&WIDTH=" ).append( request.getWidth() ); 701 sb.append( "&HEIGHT=" ).append( request.getHeight() ); 702 sb.append( "&FORMAT=" ).append( format ); 703 sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm ); 704 try { 705 IDGenerator idg = IDGenerator.getInstance(); 706 gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() ); 707 } catch ( WCSException e ) { 708 throw new InconsistentRequestException( e.getMessage() ); 709 } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) { 710 throw new InconsistentRequestException( e.getMessage() ); 711 } 712 713 LOG.logDebug( "GetCoverage request: " + sb.toString() ); 714 715 return gcr; 716 717 } 718 719 /** 720 * 721 * @param result 722 * @return the response objects 723 * @throws Exception 724 */ 725 public Object handleResponse( Object result ) 726 throws Exception { 727 728 Object theme = null; 729 if ( result instanceof ResultCoverage ) { 730 theme = handleGetCoverageResponse( (ResultCoverage) result ); 731 } else if ( result instanceof FeatureResult ) { 732 theme = handleGetFeatureResponse( (FeatureResult) result ); 733 } else if ( result instanceof GetMapResult ) { 734 theme = handleGetMapResponse( (GetMapResult) result ); 735 } else { 736 String s = Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ); 737 if ( datasource.isFailOnException() ) { 738 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 739 theme = exce; 740 } 741 } 742 return theme; 743 } 744 745 /** 746 * replaces all pixels within the passed image having a color that is defined to be transparent within their 747 * datasource with a transparent color. 748 * 749 * @param img 750 * @return modified image 751 */ 752 private BufferedImage setTransparentColors( BufferedImage img ) { 753 754 Color[] colors = null; 755 if ( datasource.getType() == AbstractDataSource.LOCALWCS ) { 756 LocalWCSDataSource ds = (LocalWCSDataSource) datasource; 757 colors = ds.getTransparentColors(); 758 } else if ( datasource.getType() == AbstractDataSource.REMOTEWCS ) { 759 RemoteWCSDataSource ds = (RemoteWCSDataSource) datasource; 760 colors = ds.getTransparentColors(); 761 } else if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) { 762 RemoteWMSDataSource ds = (RemoteWMSDataSource) datasource; 763 colors = ds.getTransparentColors(); 764 } 765 766 if ( colors != null && colors.length > 0 ) { 767 768 int[] clrs = new int[colors.length]; 769 for ( int i = 0; i < clrs.length; i++ ) { 770 clrs[i] = colors[i].getRGB(); 771 } 772 773 if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) { 774 // if the incoming image does not allow transparency 775 // it must be copyed to a image of ARGB type 776 BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB ); 777 Graphics g = tmp.getGraphics(); 778 g.drawImage( img, 0, 0, null ); 779 g.dispose(); 780 img = tmp; 781 } 782 783 // TODO 784 // should be replaced by a JAI operation 785 int w = img.getWidth(); 786 int h = img.getHeight(); 787 for ( int i = 0; i < w; i++ ) { 788 for ( int j = 0; j < h; j++ ) { 789 int col = img.getRGB( i, j ); 790 if ( shouldBeTransparent( colors, col ) ) { 791 img.setRGB( i, j, 0x00FFFFFF ); 792 } 793 } 794 } 795 796 } 797 798 return img; 799 } 800 801 /** 802 * @return true if the distance between the image color and at least of the colors to be truned to be transparent is 803 * less than 3 in an int RGB cube 804 * 805 * @param colors 806 * @param color 807 */ 808 private boolean shouldBeTransparent( Color[] colors, int color ) { 809 Color c2 = new Color( color ); 810 int r = c2.getRed(); 811 int g = c2.getGreen(); 812 int b = c2.getBlue(); 813 for ( int i = 0; i < colors.length; i++ ) { 814 int r1 = colors[i].getRed(); 815 int g1 = colors[i].getGreen(); 816 int b1 = colors[i].getBlue(); 817 if ( Math.sqrt( ( r1 - r ) * ( r1 - r ) + ( g1 - g ) * ( g1 - g ) + ( b1 - b ) * ( b1 - b ) ) < 3 ) { 818 return true; 819 } 820 } 821 return false; 822 } 823 824 /** 825 * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> 826 * from it 827 * 828 * @param response 829 * @return the response objects 830 * @throws Exception 831 */ 832 private Object handleGetMapResponse( GetMapResult response ) 833 throws Exception { 834 835 BufferedImage bi = (BufferedImage) response.getMap(); 836 837 bi = setTransparentColors( bi ); 838 GridCoverage gc = new ImageGridCoverage( null, request.getBoundingBox(), bi ); 839 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc ); 840 Theme theme = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } ); 841 return theme; 842 843 } 844 845 /** 846 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it 847 * 848 * @param response 849 * @return the response objects 850 * @throws Exception 851 */ 852 private Object handleGetFeatureResponse( FeatureResult response ) 853 throws Exception { 854 FeatureCollection fc = null; 855 856 Object o = response.getResponse(); 857 if ( o instanceof FeatureCollection ) { 858 fc = (FeatureCollection) o; 859 } else { 860 throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) ); 861 } 862 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 863 LOG.logDebug( "result: " + fc ); 864 for ( int i = 0; i < fc.size(); ++i ) { 865 outputGeometries( fc.getFeature( i ) ); 866 } 867 } 868 869 org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( layer.getName(), this.handler.getRequestCRS(), 870 fc ); 871 872 return MapFactory.createTheme( datasource.getName().getPrefixedName(), fl, new UserStyle[] { style } ); 873 } 874 875 private void outputGeometries( Feature feature ) { 876 if ( feature == null ) { 877 return; 878 } 879 FeatureType ft = feature.getFeatureType(); 880 PropertyType[] propertyTypes = ft.getProperties(); 881 for ( PropertyType pt : propertyTypes ) { 882 if ( pt.getType() == Types.FEATURE ) { 883 FeatureProperty[] fp = feature.getProperties( pt.getName() ); 884 if ( fp != null ) { 885 for ( int i = 0; i < fp.length; i++ ) { 886 outputGeometries( (Feature) fp[i].getValue() ); 887 } 888 } 889 } else if ( pt.getType() == Types.GEOMETRY ) { 890 Geometry g = feature.getDefaultGeometryPropertyValue(); 891 if ( g != null ) { 892 try { 893 LOG.logDebug( "geometrie: " + WKTAdapter.export( g ).toString() ); 894 } catch ( GeometryException e ) { 895 LOG.logDebug( "Geometry couldn't be converted to Well Known Text: " + g ); 896 } 897 } 898 } 899 } 900 } 901 902 /** 903 * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it 904 * 905 * @param response 906 * @return the response objects 907 * @throws Exception 908 */ 909 private Object handleGetCoverageResponse( ResultCoverage response ) 910 throws Exception { 911 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage(); 912 Object ro = null; 913 if ( gc != null ) { 914 BufferedImage bi = gc.getAsImage( -1, -1 ); 915 916 bi = setTransparentColors( bi ); 917 918 gc = new ImageGridCoverage( null, request.getBoundingBox(), bi ); 919 920 Dimension[] dims = handler.getConfiguration().getLayer( layer.getName() ).getDimension(); 921 DimensionValues elev = request.getDimElev(); 922 if ( dims != null ) { 923 for ( Dimension dim : dims ) { 924 if ( dim.getName().equalsIgnoreCase( "elevation" ) ) { 925 if ( elev == null && dim.getDefaultValue() != null ) { 926 request.setDimElev( new DimensionValues( dim.getDefaultValue() ) ); 927 request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" 928 + dim.getDefaultValue() + " " + dim.getUnits() ); 929 elev = request.getDimElev(); 930 } 931 if ( elev == null ) { 932 InvalidParameterValueException towrap; 933 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", 934 dim.getName() ), 935 MISSINGDIMENSIONVALUE ); 936 throw new WMSExceptionFromWCS( towrap ); 937 } 938 if ( !dim.isMultipleValues() && elev.hasMultipleValues() ) { 939 InvalidParameterValueException towrap; 940 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", 941 dim.getName() ), 942 INVALIDDIMENSIONVALUE ); 943 throw new WMSExceptionFromWCS( towrap ); 944 } 945 String singleValue = elev.values.peek().value; 946 if ( elev.values.size() == 1 && singleValue != null ) { 947 DimensionValues origValues = new DimensionValues( dim.getValues() ); 948 if ( !origValues.includesValue( singleValue ) ) { 949 if ( dim.isNearestValue() ) { 950 String nearestValue = origValues.getNearestValue( singleValue ); 951 elev.values.peek().value = nearestValue; 952 request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" 953 + nearestValue + " " + dim.getUnits() ); 954 } 955 } 956 } 957 958 if ( !new DimensionValues( dim.getValues() ).includes( elev ) ) { 959 InvalidParameterValueException towrap; 960 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_BAD_DIMENSION_VALUE" ), 961 INVALIDDIMENSIONVALUE ); 962 throw new WMSExceptionFromWCS( towrap ); 963 } 964 } 965 } 966 } 967 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc, request ); 968 969 ro = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } ); 970 } else { 971 throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE", 972 datasource.getName() ) ); 973 } 974 return ro; 975 } 976 977 class WMSExceptionFromWCS extends Exception { 978 979 private static final long serialVersionUID = 8999003296940731523L; 980 981 OGCWebServiceException wrapped; 982 983 WMSExceptionFromWCS( OGCWebServiceException towrap ) { 984 wrapped = towrap; 985 } 986 987 } 988 989 }