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 }