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