001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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: 20188 $ 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: 20188 $, $Date: 2009-10-19 12:14:30 +0200 (Mo, 19. Okt 2009) $ 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 * this can not be used if charts shale be generated dynamicly as point symbols for ( int j = 0; j < pp.size(); 372 * j++ ) { if ( !pp.get( j ).getAsString().endsWith( "$SCALE" ) ) { // $SCALE is a dynamicly created property of 373 * each feature // and can not be requested sb.append( "<PropertyName>" ).append( pp.get( j ).getAsString() ); 374 * sb.append( "</PropertyName>" ); } } 375 */ 376 377 // add filters to list 378 // BBOX filter 379 StringBuffer sb2 = new StringBuffer( 512 ); 380 sb2.append( "<ogc:BBOX>" ); 381 sb2.append( "<PropertyName>" ); 382 sb2.append( ds.getGeometryProperty().getPrefixedName() ); 383 sb2.append( "</PropertyName>" ); 384 sb2.append( exportAsBox( bbox ) ); 385 sb2.append( "</ogc:BBOX>" ); 386 filters.add( sb2 ); 387 // custom filter for property value 388 if ( useCustomFilter ) { 389 sb2 = new StringBuffer( 512 ); 390 appendCustomFilter( sb2, filterProperty, filterValue ); 391 filters.add( sb2 ); 392 } 393 // filters of query 394 Query query = ds.getQuery(); 395 if ( query != null ) { 396 Filter filter = query.getFilter(); 397 filters.addAll( extractFilters( filter ) ); 398 SortProperty[] sps = query.getSortProperties(); 399 if ( sps != null ) { 400 for ( SortProperty sp : sps ) { 401 sortProperties.add( extractSortProperty( sp ) ); 402 } 403 } 404 } 405 // filters from SLD 406 if ( layer != null ) { 407 LayerFeatureConstraints lfc = layer.getLayerFeatureConstraints(); 408 if ( lfc != null ) { 409 FeatureTypeConstraint[] fcs = lfc.getFeatureTypeConstraint(); 410 if ( fcs != null ) { 411 for ( FeatureTypeConstraint fc : fcs ) { 412 filters.addAll( extractFilters( fc.getFilter() ) ); 413 } 414 } 415 } 416 } 417 418 // filters from the dimensions 419 Layer lay = handler.getConfiguration().getLayer( layer.getName() ); 420 Map<String, String> dimProps = ds.getDimProps(); 421 if ( lay.getDimension() != null && dimProps != null ) { 422 for ( Dimension dim : lay.getDimension() ) { 423 if ( dim.getName().equals( "time" ) ) { 424 filters.add( handleDimension( request.getDimTime(), dim, dimProps.get( "time" ) ) ); 425 } 426 if ( dim.getName().equals( "elevation" ) ) { 427 filters.add( handleDimension( request.getDimElev(), dim, dimProps.get( "elevation" ) ) ); 428 } 429 } 430 } 431 432 // actually append the filters 433 sb.append( "<ogc:Filter>" ); 434 if ( filters.size() > 1 ) { 435 sb.append( "<ogc:And>" ); 436 } 437 for ( StringBuffer s : filters ) { 438 sb.append( s ); 439 } 440 if ( filters.size() > 1 ) { 441 sb.append( "</ogc:And>" ); 442 } 443 sb.append( "</ogc:Filter>" ); 444 if ( sortProperties.size() > 0 ) { 445 sb.append( "<ogc:SortBy>" ); 446 for ( StringBuffer s : sortProperties ) { 447 sb.append( s ); 448 } 449 sb.append( "</ogc:SortBy>" ); 450 } 451 sb.append( "</Query></GetFeature>" ); 452 453 // create dom representation of the request 454 Document doc = XMLTools.parse( new StringReader( sb.toString() ) ); 455 456 if ( LOG.isDebug() ) { 457 LOG.logDebug( "GetFeature request: " 458 + new XMLFragment( doc, "http://www.systemid.org" ).getAsPrettyString() ); 459 } 460 461 // create OGCWebServiceEvent object 462 IDGenerator idg = IDGenerator.getInstance(); 463 GetFeature gfr = GetFeature.create( "" + idg.generateUniqueID(), doc.getDocumentElement() ); 464 465 return gfr; 466 } 467 468 private StringBuffer handleDimension( DimensionValues values, Dimension dim, String dimProp ) 469 throws OGCWebServiceException { 470 if ( values == null && dim.getDefaultValue() != null ) { 471 values = new DimensionValues( dim.getDefaultValue() ); 472 request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" + dim.getDefaultValue() + " " 473 + dim.getUnits() ); 474 } 475 476 if ( values == null ) { 477 throw new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", dim.getName() ), 478 MISSINGDIMENSIONVALUE ); 479 } 480 if ( !dim.isMultipleValues() && values.hasMultipleValues() ) { 481 throw new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", dim.getName() ), 482 INVALIDDIMENSIONVALUE ); 483 } 484 485 String singleValue = values.values.peek().value; 486 if ( values.values.size() == 1 && singleValue != null ) { 487 DimensionValues origValues = new DimensionValues( dim.getValues() ); 488 if ( !origValues.includesValue( singleValue ) ) { 489 if ( dim.isNearestValue() ) { 490 String nearestValue = origValues.getNearestValue( singleValue ); 491 values.values.peek().value = nearestValue; 492 request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" + nearestValue + " " 493 + dim.getUnits() ); 494 } 495 } 496 } 497 498 LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(); 499 500 for ( DimensionValue val : values.values ) { 501 StringBuffer sb = new StringBuffer( 512 ); 502 if ( val.value != null ) { 503 sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ).append( dimProp ); 504 sb.append( "</ogc:PropertyName><ogc:Literal>" ).append( val.value ); 505 sb.append( "</ogc:Literal></ogc:PropertyIsEqualTo>" ); 506 } else { 507 sb.append( "<ogc:PropertyIsBetween><ogc:PropertyName>" ).append( dimProp ); 508 sb.append( "</ogc:PropertyName><ogc:LowerBoundary><ogc:Literal>" ).append( val.low ); 509 sb.append( "</ogc:Literal></ogc:LowerBoundary><ogc:UpperBoundary><ogc:Literal>" ).append( val.high ); 510 sb.append( "</ogc:Literal></ogc:UpperBoundary></ogc:PropertyIsBetween>" ); 511 } 512 filters.add( sb ); 513 } 514 515 if ( filters.size() == 1 ) { 516 return filters.poll(); 517 } 518 519 StringBuffer sb = new StringBuffer( 512 * filters.size() ); 520 sb.append( "<ogc:Or>" ); 521 while ( filters.size() > 0 ) { 522 sb.append( filters.poll() ); 523 } 524 sb.append( "</ogc:Or>" ); 525 return sb; 526 } 527 528 private static StringBuffer extractSortProperty( SortProperty sp ) { 529 StringBuffer sb = new StringBuffer(); 530 sb.append( "<ogc:SortProperty>" ); 531 sb.append( "<ogc:PropertyName>" ); 532 sb.append( sp.getSortProperty().toString() ); 533 sb.append( "</ogc:PropertyName>" ); 534 sb.append( "<ogc:SortOrder>" ); 535 sb.append( sp.getSortOrder() ? "ASC" : "DESC" ); 536 sb.append( "</ogc:SortOrder>" ); 537 sb.append( "</ogc:SortProperty>" ); 538 539 return sb; 540 } 541 542 private static LinkedList<StringBuffer> extractFilters( Filter filter ) { 543 LinkedList<StringBuffer> filters = new LinkedList<StringBuffer>(); 544 545 if ( filter instanceof ComplexFilter ) { 546 filters.add( ( ( (ComplexFilter) filter ).getOperation() ).toXML() ); 547 } 548 if ( filter instanceof FeatureFilter ) { 549 ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds(); 550 for ( int i = 0; i < featureIds.size(); i++ ) { 551 FeatureId fid = featureIds.get( i ); 552 filters.add( fid.toXML() ); 553 } 554 } 555 556 return filters; 557 } 558 559 private static void appendCustomFilter( StringBuffer sb, String name, String value ) { 560 String[] vals = value.split( "," ); 561 if ( vals.length > 1 ) { 562 sb.append( "<ogc:Or>" ); 563 } 564 for ( String val : vals ) { 565 sb.append( "<ogc:PropertyIsEqualTo><ogc:PropertyName>" ); 566 sb.append( name ).append( "</ogc:PropertyName><ogc:Literal>" ); 567 sb.append( val ).append( "</ogc:Literal></ogc:PropertyIsEqualTo>" ); 568 } 569 if ( vals.length > 1 ) { 570 sb.append( "</ogc:Or>" ); 571 } 572 } 573 574 /** 575 * transforms the requested BBOX into the DefaultSRS of the assigned feature type 576 * 577 * @param ds 578 * @return the envelope 579 * @throws OGCWebServiceException 580 * @throws CRSTransformationException 581 * @throws UnknownCRSException 582 */ 583 private Envelope transformBBOX( LocalWFSDataSource ds ) 584 throws OGCWebServiceException, CRSTransformationException, UnknownCRSException { 585 Envelope bbox = request.getBoundingBox(); 586 // transform request bounding box to the coordinate reference 587 // system the WFS holds the data if requesting CRS and WFS-Data 588 // crs are different 589 OGCWebService service = ds.getOGCWebService(); 590 WFSCapabilities capa; 591 if ( service instanceof RemoteWFService ) { 592 RemoteWFService wfs = (RemoteWFService) ds.getOGCWebService(); 593 capa = wfs.getWFSCapabilities(); 594 } else { 595 WFService wfs = (WFService) service; 596 capa = wfs.getCapabilities(); 597 } 598 // WFSCapabilities capa = (WFSCapabilities)wfs.getWFSCapabilities(); 599 QualifiedName gn = ds.getName(); 600 WFSFeatureType ft = capa.getFeatureTypeList().getFeatureType( gn ); 601 602 if ( ft == null ) { 603 throw new OGCWebServiceException( Messages.getMessage( "WMS_UNKNOWNFT", ds.getName() ) ); 604 } 605 606 // enable different formatations of the crs encoding for GML geometries 607 String GML_SRS = "http://www.opengis.net/gml/srs/"; 608 String old_gml_srs = ft.getDefaultSRS().toASCIIString(); 609 String old_srs; 610 if ( old_gml_srs.startsWith( GML_SRS ) ) { 611 old_srs = old_gml_srs.substring( 31 ).replace( '#', ':' ).toUpperCase(); 612 } else { 613 old_srs = old_gml_srs; 614 } 615 616 String new_srs = request.getSrs(); 617 String new_gml_srs; 618 if ( old_gml_srs.startsWith( GML_SRS ) ) { 619 new_gml_srs = GML_SRS + new_srs.replace( ':', '#' ).toLowerCase(); 620 } else { 621 new_gml_srs = new_srs; 622 } 623 624 if ( !( old_srs.equalsIgnoreCase( new_gml_srs ) ) ) { 625 GeoTransformer transformer = new GeoTransformer( CRSFactory.create( old_srs ) ); 626 bbox = transformer.transform( bbox, this.handler.getRequestCRS() ); 627 } 628 return bbox; 629 } 630 631 /** 632 * creates a getCoverage request considering the getMap request and the filterconditions defined in the submitted 633 * <tt>DataSource</tt> object The request will be encapsualted within a <tt>OGCWebServiceEvent</tt>. 634 * 635 * @param ds 636 * the datasource 637 * @param request 638 * the GetMap operation 639 * @return GetCoverage request object 640 * @throws InconsistentRequestException 641 */ 642 protected static GetCoverage createGetCoverageRequest( AbstractDataSource ds, GetMap request ) 643 throws InconsistentRequestException { 644 645 Envelope bbox = request.getBoundingBox(); 646 647 double xres = bbox.getWidth() / request.getWidth(); 648 double yres = bbox.getHeight() / request.getHeight(); 649 650 WorldFile tmpWorldFile = new WorldFile( xres, yres, 0.0, 0.0, bbox, WorldFile.TYPE.OUTER ); 651 bbox = tmpWorldFile.getEnvelope( WorldFile.TYPE.CENTER ); 652 653 GetCoverage gcr = ( (LocalWCSDataSource) ds ).getGetCoverageRequest(); 654 655 String crs = request.getSrs(); 656 // if (gcr != null && gcr.getDomainSubset().getRequestSRS() != null) { 657 // crs = gcr.getDomainSubset().getRequestSRS().getCode(); 658 // } 659 String format = request.getFormat(); 660 int pos = format.indexOf( '/' ); 661 if ( pos > -1 ) 662 format = format.substring( pos + 1, format.length() ); 663 if ( gcr != null && !"%default%".equals( gcr.getOutput().getFormat().getCode() ) ) { 664 format = gcr.getOutput().getFormat().getCode(); 665 } 666 if ( format.indexOf( "svg" ) > -1 ) { 667 format = "tiff"; 668 } 669 if ( format.startsWith( "png" ) ) { 670 format = "png"; 671 } 672 673 String version = "1.0.0"; 674 if ( gcr != null && gcr.getVersion() != null ) { 675 version = gcr.getVersion(); 676 } 677 String lay = ds.getName().getPrefixedName(); 678 if ( gcr != null && !"%default%".equals( gcr.getSourceCoverage() ) ) { 679 lay = gcr.getSourceCoverage(); 680 } 681 String ipm = null; 682 if ( gcr != null && gcr.getInterpolationMethod() != null ) { 683 ipm = gcr.getInterpolationMethod().value; 684 } 685 686 // TODO 687 // handle range sets e.g. time 688 // note that elevation is now handled after the results are in (see the handleGetCoverageResponse method below) 689 StringBuffer sb = new StringBuffer( 1000 ); 690 sb.append( "service=WCS&request=GetCoverage" ); 691 sb.append( "&version=" ).append( version ); 692 sb.append( "&COVERAGE=" ).append( lay ); 693 sb.append( "&crs=" ).append( crs ); 694 sb.append( "&response_crs=" ).append( crs ); 695 sb.append( "&BBOX=" ).append( bbox.getMin().getX() ).append( ',' ); 696 sb.append( bbox.getMin().getY() ).append( ',' ).append( bbox.getMax().getX() ); 697 sb.append( ',' ).append( bbox.getMax().getY() ); 698 sb.append( "&WIDTH=" ).append( request.getWidth() ); 699 sb.append( "&HEIGHT=" ).append( request.getHeight() ); 700 sb.append( "&FORMAT=" ).append( format ); 701 sb.append( "&INTERPOLATIONMETHOD=" ).append( ipm ); 702 try { 703 IDGenerator idg = IDGenerator.getInstance(); 704 gcr = GetCoverage.create( "id" + idg.generateUniqueID(), sb.toString() ); 705 } catch ( WCSException e ) { 706 throw new InconsistentRequestException( e.getMessage() ); 707 } catch ( org.deegree.ogcwebservices.OGCWebServiceException e ) { 708 throw new InconsistentRequestException( e.getMessage() ); 709 } 710 711 LOG.logDebug( "GetCoverage request: " + sb.toString() ); 712 713 return gcr; 714 715 } 716 717 /** 718 * 719 * @param result 720 * @return the response objects 721 * @throws Exception 722 */ 723 public Object handleResponse( Object result ) 724 throws Exception { 725 726 Object theme = null; 727 if ( result instanceof ResultCoverage ) { 728 theme = handleGetCoverageResponse( (ResultCoverage) result ); 729 } else if ( result instanceof FeatureResult ) { 730 theme = handleGetFeatureResponse( (FeatureResult) result ); 731 } else if ( result instanceof GetMapResult ) { 732 theme = handleGetMapResponse( (GetMapResult) result ); 733 } else { 734 String s = Messages.getMessage( "WMS_UNKNOWNRESPONSEFORMAT" ); 735 if ( datasource.isFailOnException() ) { 736 OGCWebServiceException exce = new OGCWebServiceException( getClass().getName(), s ); 737 theme = exce; 738 } 739 } 740 return theme; 741 } 742 743 /** 744 * replaces all pixels within the passed image having a color that is defined to be transparent within their 745 * datasource with a transparent color. 746 * 747 * @param img 748 * @return modified image 749 */ 750 private BufferedImage setTransparentColors( BufferedImage img ) { 751 752 Color[] colors = null; 753 if ( datasource.getType() == AbstractDataSource.LOCALWCS ) { 754 LocalWCSDataSource ds = (LocalWCSDataSource) datasource; 755 colors = ds.getTransparentColors(); 756 } else if ( datasource.getType() == AbstractDataSource.REMOTEWCS ) { 757 RemoteWCSDataSource ds = (RemoteWCSDataSource) datasource; 758 colors = ds.getTransparentColors(); 759 } else if ( datasource.getType() == AbstractDataSource.REMOTEWMS ) { 760 RemoteWMSDataSource ds = (RemoteWMSDataSource) datasource; 761 colors = ds.getTransparentColors(); 762 } 763 764 if ( colors != null && colors.length > 0 ) { 765 766 int[] clrs = new int[colors.length]; 767 for ( int i = 0; i < clrs.length; i++ ) { 768 clrs[i] = colors[i].getRGB(); 769 } 770 771 if ( img.getType() != BufferedImage.TYPE_INT_ARGB ) { 772 // if the incoming image does not allow transparency 773 // it must be copyed to a image of ARGB type 774 BufferedImage tmp = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB ); 775 Graphics g = tmp.getGraphics(); 776 g.drawImage( img, 0, 0, null ); 777 g.dispose(); 778 img = tmp; 779 } 780 781 // TODO 782 // should be replaced by a JAI operation 783 int w = img.getWidth(); 784 int h = img.getHeight(); 785 for ( int i = 0; i < w; i++ ) { 786 for ( int j = 0; j < h; j++ ) { 787 int col = img.getRGB( i, j ); 788 if ( shouldBeTransparent( colors, col ) ) { 789 img.setRGB( i, j, 0x00FFFFFF ); 790 } 791 } 792 } 793 794 } 795 796 return img; 797 } 798 799 /** 800 * @return true if the distance between the image color and at least of the colors to be truned to be transparent is 801 * less than 3 in an int RGB cube 802 * 803 * @param colors 804 * @param color 805 */ 806 private boolean shouldBeTransparent( Color[] colors, int color ) { 807 Color c2 = new Color( color ); 808 int r = c2.getRed(); 809 int g = c2.getGreen(); 810 int b = c2.getBlue(); 811 for ( int i = 0; i < colors.length; i++ ) { 812 int r1 = colors[i].getRed(); 813 int g1 = colors[i].getGreen(); 814 int b1 = colors[i].getBlue(); 815 if ( Math.sqrt( ( r1 - r ) * ( r1 - r ) + ( g1 - g ) * ( g1 - g ) + ( b1 - b ) * ( b1 - b ) ) < 3 ) { 816 return true; 817 } 818 } 819 return false; 820 } 821 822 /** 823 * handles the response of a cascaded WMS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> 824 * from it 825 * 826 * @param response 827 * @return the response objects 828 * @throws Exception 829 */ 830 private Object handleGetMapResponse( GetMapResult response ) 831 throws Exception { 832 833 BufferedImage bi = (BufferedImage) response.getMap(); 834 835 bi = setTransparentColors( bi ); 836 GridCoverage gc = new ImageGridCoverage( null, request.getBoundingBox(), bi ); 837 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc ); 838 Theme theme = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } ); 839 return theme; 840 841 } 842 843 /** 844 * handles the response of a WFS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it 845 * 846 * @param response 847 * @return the response objects 848 * @throws Exception 849 */ 850 private Object handleGetFeatureResponse( FeatureResult response ) 851 throws Exception { 852 FeatureCollection fc = null; 853 854 Object o = response.getResponse(); 855 if ( o instanceof FeatureCollection ) { 856 fc = (FeatureCollection) o; 857 } else { 858 throw new Exception( Messages.getMessage( "WMS_UNKNOWNDATAFORMATFT" ) ); 859 } 860 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) { 861 LOG.logDebug( "result: " + fc ); 862 for ( int i = 0; i < fc.size(); ++i ) { 863 outputGeometries( fc.getFeature( i ) ); 864 } 865 } 866 867 org.deegree.graphics.Layer fl = MapFactory.createFeatureLayer( layer.getName(), this.handler.getRequestCRS(), 868 fc ); 869 870 return MapFactory.createTheme( datasource.getName().getPrefixedName(), fl, new UserStyle[] { style } ); 871 } 872 873 private void outputGeometries( Feature feature ) { 874 if ( feature == null ) { 875 return; 876 } 877 FeatureType ft = feature.getFeatureType(); 878 PropertyType[] propertyTypes = ft.getProperties(); 879 for ( PropertyType pt : propertyTypes ) { 880 if ( pt.getType() == Types.FEATURE ) { 881 FeatureProperty[] fp = feature.getProperties( pt.getName() ); 882 if ( fp != null ) { 883 for ( int i = 0; i < fp.length; i++ ) { 884 outputGeometries( (Feature) fp[i].getValue() ); 885 } 886 } 887 } else if ( pt.getType() == Types.GEOMETRY ) { 888 Geometry g = feature.getDefaultGeometryPropertyValue(); 889 if ( g != null ) { 890 try { 891 LOG.logDebug( "geometrie: " + WKTAdapter.export( g ).toString() ); 892 } catch ( GeometryException e ) { 893 LOG.logDebug( "Geometry couldn't be converted to Well Known Text: " + g ); 894 } 895 } 896 } 897 } 898 } 899 900 /** 901 * handles the response of a WCS and calls a factory to create <tt>DisplayElement</tt> and a <tt>Theme</tt> from it 902 * 903 * @param response 904 * @return the response objects 905 * @throws Exception 906 */ 907 private Object handleGetCoverageResponse( ResultCoverage response ) 908 throws Exception { 909 ImageGridCoverage gc = (ImageGridCoverage) response.getCoverage(); 910 Object ro = null; 911 if ( gc != null ) { 912 BufferedImage bi = gc.getAsImage( -1, -1 ); 913 914 bi = setTransparentColors( bi ); 915 916 gc = new ImageGridCoverage( null, request.getBoundingBox(), bi ); 917 918 Dimension[] dims = handler.getConfiguration().getLayer( layer.getName() ).getDimension(); 919 DimensionValues elev = request.getDimElev(); 920 if ( dims != null ) { 921 for ( Dimension dim : dims ) { 922 if ( dim.getName().equalsIgnoreCase( "elevation" ) ) { 923 if ( elev == null && dim.getDefaultValue() != null ) { 924 request.setDimElev( new DimensionValues( dim.getDefaultValue() ) ); 925 request.warningHeaders.add( "99 Default value used: " + dim.getName() + "=" 926 + dim.getDefaultValue() + " " + dim.getUnits() ); 927 elev = request.getDimElev(); 928 } 929 if ( elev == null ) { 930 InvalidParameterValueException towrap; 931 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_MISSING_DIMENSION_VALUE", 932 dim.getName() ), 933 MISSINGDIMENSIONVALUE ); 934 throw new WMSExceptionFromWCS( towrap ); 935 } 936 if ( !dim.isMultipleValues() && elev.hasMultipleValues() ) { 937 InvalidParameterValueException towrap; 938 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_NO_MULTIPLE_VALUES", 939 dim.getName() ), 940 INVALIDDIMENSIONVALUE ); 941 throw new WMSExceptionFromWCS( towrap ); 942 } 943 String singleValue = elev.values.peek().value; 944 if ( elev.values.size() == 1 && singleValue != null ) { 945 DimensionValues origValues = new DimensionValues( dim.getValues() ); 946 if ( !origValues.includesValue( singleValue ) ) { 947 if ( dim.isNearestValue() ) { 948 String nearestValue = origValues.getNearestValue( singleValue ); 949 elev.values.peek().value = nearestValue; 950 request.warningHeaders.add( "99 Nearest value used: " + dim.getName() + "=" 951 + nearestValue + " " + dim.getUnits() ); 952 } 953 } 954 } 955 956 if ( !new DimensionValues( dim.getValues() ).includes( elev ) ) { 957 InvalidParameterValueException towrap; 958 towrap = new InvalidParameterValueException( "GetMap", get( "WMS_BAD_DIMENSION_VALUE" ), 959 INVALIDDIMENSIONVALUE ); 960 throw new WMSExceptionFromWCS( towrap ); 961 } 962 } 963 } 964 } 965 org.deegree.graphics.Layer rl = MapFactory.createRasterLayer( layer.getName(), gc, request ); 966 967 ro = MapFactory.createTheme( datasource.getName().getPrefixedName(), rl, new UserStyle[] { style } ); 968 } else { 969 throw new OGCWebServiceException( getClass().getName(), Messages.getMessage( "WMS_NOCOVERAGE", 970 datasource.getName() ) ); 971 } 972 return ro; 973 } 974 975 class WMSExceptionFromWCS extends Exception { 976 977 private static final long serialVersionUID = 8999003296940731523L; 978 979 OGCWebServiceException wrapped; 980 981 WMSExceptionFromWCS( OGCWebServiceException towrap ) { 982 wrapped = towrap; 983 } 984 985 } 986 987 }