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