001    //$$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wpvs/configuration/WPVSConfiguration.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    
037    package org.deegree.ogcwebservices.wpvs.configuration;
038    
039    import java.awt.GraphicsConfigTemplate;
040    import java.awt.GraphicsConfiguration;
041    import java.awt.GraphicsDevice;
042    import java.awt.GraphicsEnvironment;
043    import java.awt.image.BufferedImage;
044    import java.io.File;
045    import java.io.IOException;
046    import java.io.StringReader;
047    import java.util.ArrayList;
048    import java.util.Arrays;
049    import java.util.HashMap;
050    import java.util.List;
051    import java.util.Map;
052    import java.util.Set;
053    import java.util.Vector;
054    
055    import javax.imageio.ImageIO;
056    import javax.media.j3d.Canvas3D;
057    import javax.media.j3d.GraphicsConfigTemplate3D;
058    import javax.media.j3d.View;
059    import javax.vecmath.Point3d;
060    
061    import org.deegree.datatypes.Code;
062    import org.deegree.datatypes.QualifiedName;
063    import org.deegree.framework.log.ILogger;
064    import org.deegree.framework.log.LoggerFactory;
065    import org.deegree.framework.util.CharsetUtils;
066    import org.deegree.framework.util.IDGenerator;
067    import org.deegree.framework.xml.XMLTools;
068    import org.deegree.i18n.Messages;
069    import org.deegree.model.coverage.grid.ImageGridCoverage;
070    import org.deegree.model.crs.CRSTransformationException;
071    import org.deegree.model.crs.CoordinateSystem;
072    import org.deegree.model.crs.GeoTransformer;
073    import org.deegree.model.crs.UnknownCRSException;
074    import org.deegree.model.feature.FeatureCollection;
075    import org.deegree.model.filterencoding.ComplexFilter;
076    import org.deegree.model.filterencoding.FeatureFilter;
077    import org.deegree.model.filterencoding.FeatureId;
078    import org.deegree.model.filterencoding.Filter;
079    import org.deegree.model.spatialschema.Envelope;
080    import org.deegree.model.spatialschema.GMLGeometryAdapter;
081    import org.deegree.model.spatialschema.Position;
082    import org.deegree.ogcbase.PropertyPath;
083    import org.deegree.ogcwebservices.OGCWebServiceException;
084    import org.deegree.ogcwebservices.getcapabilities.Contents;
085    import org.deegree.ogcwebservices.getcapabilities.OperationsMetadata;
086    import org.deegree.ogcwebservices.getcapabilities.ServiceIdentification;
087    import org.deegree.ogcwebservices.getcapabilities.ServiceProvider;
088    import org.deegree.ogcwebservices.wcs.WCSException;
089    import org.deegree.ogcwebservices.wcs.WCService;
090    import org.deegree.ogcwebservices.wcs.getcoverage.DomainSubset;
091    import org.deegree.ogcwebservices.wcs.getcoverage.GetCoverage;
092    import org.deegree.ogcwebservices.wcs.getcoverage.Output;
093    import org.deegree.ogcwebservices.wcs.getcoverage.ResultCoverage;
094    import org.deegree.ogcwebservices.wcs.getcoverage.SpatialSubset;
095    import org.deegree.ogcwebservices.wfs.WFService;
096    import org.deegree.ogcwebservices.wfs.operation.FeatureResult;
097    import org.deegree.ogcwebservices.wfs.operation.GetFeature;
098    import org.deegree.ogcwebservices.wpvs.capabilities.Dataset;
099    import org.deegree.ogcwebservices.wpvs.capabilities.ElevationModel;
100    import org.deegree.ogcwebservices.wpvs.capabilities.WPVSCapabilities;
101    import org.deegree.ogcwebservices.wpvs.j3d.PointsToPointListFactory;
102    import org.deegree.processing.raster.converter.Image2RawData;
103    import org.w3c.dom.Document;
104    
105    /**
106     * This class represents a <code>WPVSConfiguration</code> object.
107     * 
108     * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
109     * @author last edited by: $Author: rbezema $
110     * 
111     *         $Revision: 20603 $, $Date: 2009-11-05 16:26:23 +0100 (Do, 05. Nov 2009) $
112     * 
113     */
114    public class WPVSConfiguration extends WPVSCapabilities {
115    
116        private static ILogger LOG = LoggerFactory.getLogger( WPVSConfiguration.class );
117    
118        /**
119         *
120         */
121        private static final long serialVersionUID = 3699085834869705611L;
122    
123        private WPVSDeegreeParams deegreeParams;
124    
125        private double smallestMinimalScaleDenomiator;
126    
127        // set if the configured datasets were searched.
128        private static boolean searchedForElevModel = false;
129    
130        // pool for the canvasses
131        private static List<Canvas3D> canvasPool = new ArrayList<Canvas3D>();
132    
133        private static Vector<Canvas3D> inUseCanvases = new Vector<Canvas3D>();
134    
135        // just some strings for getting the gpu-properties
136        private final static String tusm = "textureUnitStateMax";
137    
138        private final static String twm = "textureWidthMax";
139    
140        /**
141         * Is set to the maximum number of texture units accessable to the wpvs.
142         */
143        public static int availableTextureUnitStates = 1;
144    
145        /**
146         * Is set to the maximum size of a texture supported by the canvas3d.
147         */
148        public static int texture2DMaxSize = 1024;
149    
150        /**
151         * The size of a wfs/wms/wcs request if larger as the available texture size it will be the texture size (read from
152         * the gpu).
153         */
154        public static int MAX_REQUEST_SIZE;
155    
156        /**
157         * The elevation model to check for the height above the terrain.
158         */
159        public static AbstractDataSource largestElevModel = null;
160    
161        // holding the z values with heightMap[y][x]
162        private static float[][] heightMap;
163    
164        // scale mapping the width of the configured bbox to the width of the heightmap
165        private static double scaleMapWidth;
166    
167        // scale mapping the height of the configured bbox to the height of the heightmap
168        private static double scaleMapHeight;
169    
170        // actual width of the heightMap == heightmap[0].length
171        private static int mapWidth;
172    
173        // actual height of the heightMap == heightmap.length
174        private static int mapHeight;
175    
176        // the found minimalHeight for the terrain or the configuredMinTerrainHeight.
177        private static double globalMinimalHeight;
178    
179        // the configured bbox of the top dataset.
180        private static Envelope configuredBBox;
181    
182        static {
183            canvasPool.add( createOffscreenCanvas3D() );
184        }
185    
186        /**
187         * @param version
188         *            the Version of this wpvs
189         * @param updateSequence
190         *            optional needed for clients who want to do caching (ogc-spec)
191         * @param serviceIdentification
192         * @param serviceProvider
193         * @param operationsMetadata
194         * @param contents
195         * @param dataset
196         * @param wpvsParams
197         *            deegree specific parameters.
198         * @param smallestMinimalScaleDenomiator
199         *            of all datasources, it is needed to calculate the smallest resolutionstripe possible.
200         */
201        public WPVSConfiguration( String version, String updateSequence, ServiceIdentification serviceIdentification,
202                                  ServiceProvider serviceProvider, OperationsMetadata operationsMetadata,
203                                  Contents contents, Dataset dataset, WPVSDeegreeParams wpvsParams,
204                                  double smallestMinimalScaleDenomiator ) {
205    
206            super( version, updateSequence, serviceIdentification, serviceProvider, operationsMetadata, contents, dataset );
207            this.deegreeParams = wpvsParams;
208            // int size = Integer.MAX_VALUE;// deegreeParams.getMaxRequestSize();
209            int size = deegreeParams.getMaxTextureDimension(); // deegreeParams.getMaxRequestSize();
210            if ( size == Integer.MAX_VALUE ) {
211                MAX_REQUEST_SIZE = texture2DMaxSize;
212            } else {
213                MAX_REQUEST_SIZE = size;
214                if ( MAX_REQUEST_SIZE > texture2DMaxSize ) {
215                    LOG.logWarning( "The specified max request size value (of the deeegree params section) is larger then the possible texture size of your graphics-card, therefore setting it to: "
216                                    + texture2DMaxSize );
217                    MAX_REQUEST_SIZE = texture2DMaxSize;
218                }
219            }
220            // set the minmalScaleDenominator according to the request-size
221            this.smallestMinimalScaleDenomiator = ( (double) deegreeParams.getMaxViewWidth() ) / MAX_REQUEST_SIZE;
222            LOG.logDebug( "Smallest denomi: " + this.smallestMinimalScaleDenomiator );
223            if ( Double.isInfinite( smallestMinimalScaleDenomiator ) || smallestMinimalScaleDenomiator < 0
224                 || smallestMinimalScaleDenomiator > 1 ) {
225                this.smallestMinimalScaleDenomiator = 1;
226            }
227    
228            // create the global height map if it does not exist already
229            if ( !searchedForElevModel && largestElevModel == null ) {
230                synchronized ( canvasPool ) {
231                    if ( !searchedForElevModel ) {
232                        largestElevModel = findLargestElevModel();
233                        searchedForElevModel = true;
234                        if ( largestElevModel != null ) {
235                            Dataset topSet = getDataset();
236                            Envelope env = topSet.getWgs84BoundingBox();
237                            CoordinateSystem[] definedCRSs = topSet.getCrs();
238                            CoordinateSystem defaultCRS = null;
239                            if ( definedCRSs != null && definedCRSs.length > 0 ) {
240                                defaultCRS = definedCRSs[0];
241                                if ( definedCRSs.length > 1 ) {
242                                    LOG.logInfo( "Using first defined crs: " + defaultCRS
243                                                 + " to convert latlon (wgs84) coordinates to." );
244                                }
245                                try {
246                                    GeoTransformer gt = new GeoTransformer( defaultCRS );
247                                    configuredBBox = gt.transform( env, "EPSG:4326" );
248                                } catch ( UnknownCRSException e ) {
249                                    LOG.logError( e.getMessage(), e );
250                                } catch ( CRSTransformationException e ) {
251                                    LOG.logError( e.getMessage(), e );
252                                }
253                                // check if the admin has configured a minimal dgm resolution, if so the request and
254                                // response resolution for the dgm must be set.
255                                int requestWidth = MAX_REQUEST_SIZE;
256                                int requestHeight = MAX_REQUEST_SIZE;
257                                if ( configuredBBox.getWidth() >= configuredBBox.getHeight() ) {
258                                    requestHeight = (int) Math.floor( ( configuredBBox.getHeight() / configuredBBox.getWidth() )
259                                                                      * MAX_REQUEST_SIZE );
260                                } else {
261                                    requestWidth = (int) Math.floor( ( configuredBBox.getWidth() / configuredBBox.getHeight() )
262                                                                     * MAX_REQUEST_SIZE );
263                                }
264                                LOG.logDebug( "Setting globalHeightmap requestWidth: " + requestWidth );
265                                LOG.logDebug( "Setting globalHeightmap requestHeight: " + requestHeight );
266    
267                                // Set the static members for easy global access
268                                mapHeight = requestHeight;
269                                mapWidth = requestWidth;
270    
271                                scaleMapWidth = mapWidth / configuredBBox.getWidth();
272                                scaleMapHeight = mapHeight / configuredBBox.getHeight();
273    
274                                if ( largestElevModel instanceof LocalWCSDataSource ) {
275                                    heightMap = invokeWCS( (LocalWCSDataSource) largestElevModel, configuredBBox,
276                                                           requestWidth, requestHeight );
277                                } else if ( largestElevModel instanceof LocalWFSDataSource ) {
278                                    heightMap = invokeWFS( (LocalWFSDataSource) largestElevModel, configuredBBox,
279                                                           requestWidth, requestHeight );
280                                }
281    
282                                if ( heightMap.length == 0 ) {
283                                    LOG.logWarning( "The creation of the global heightmap has failed." );
284                                    globalMinimalHeight = getDeegreeParams().getMinimalTerrainHeight();
285                                } else {
286                                    float max = Float.MIN_VALUE;
287                                    globalMinimalHeight = Double.MAX_VALUE;
288                                    for ( int y = 0; y < requestHeight; ++y ) {
289                                        for ( int x = 0; x < requestWidth; ++x ) {
290                                            float height = heightMap[y][x];
291                                            globalMinimalHeight = Math.min( globalMinimalHeight, height );
292                                            max = Math.max( max, height );
293                                        }
294                                    }
295                                    if ( globalMinimalHeight < getDeegreeParams().getMinimalTerrainHeight() ) {
296                                        LOG.logDebug( "Setting found globalMinimalHeight: " + globalMinimalHeight
297                                                      + " to configured minmalTerrainHeight of: "
298                                                      + getDeegreeParams().getMinimalTerrainHeight() );
299                                        globalMinimalHeight = getDeegreeParams().getMinimalTerrainHeight();
300                                    }
301                                    if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
302                                        BufferedImage img = new BufferedImage( requestWidth, requestHeight,
303                                                                               BufferedImage.TYPE_INT_RGB );
304                                        LOG.logDebug( "global maximumHeight: " + max );
305                                        LOG.logDebug( "global minimalHeight: " + globalMinimalHeight );
306    
307                                        double scale = ( 1 / ( max - globalMinimalHeight ) ) * 255;
308                                        for ( int y = 0; y < requestHeight; ++y ) {
309                                            for ( int x = 0; x < requestWidth; ++x ) {
310                                                float height = heightMap[y][x];
311                                                byte first = (byte) Math.floor( height * scale );
312                                                int color = first;
313                                                color |= ( color << 8 );
314                                                color |= ( color << 16 );
315    
316                                                img.setRGB( x, y, color );
317                                            }
318    
319                                        }
320                                        try {
321                                            File f = File.createTempFile( "global_heightmap_response", ".png" );
322                                            LOG.logDebug( "creating tmpfile for global heightmap with name: "
323                                                          + f.toString() );
324                                            f.deleteOnExit();
325                                            ImageIO.write( img, "png", f );
326                                        } catch ( IOException e ) {
327                                            LOG.logError( e.getMessage(), e );
328                                        }
329                                    }
330    
331                                }
332    
333                            }
334                        }
335                    }
336                    canvasPool.notifyAll();
337                }
338                if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
339                    if ( largestElevModel != null ) {
340                        LOG.logDebug( "found elev-model: " + largestElevModel );
341                    }
342                }
343            }
344        }
345    
346        /**
347         * @param dataSource
348         * @param env
349         * @param requestHeight
350         * @param requestWidth
351         * @return the wfs-feature-points mapped to the heightmap, or an empty map if no features were found.
352         */
353        private float[][] invokeWFS( LocalWFSDataSource dataSource, Envelope env, int requestWidth, int requestHeight ) {
354            float[][] result = new float[0][0];
355            WFService service = null;
356            try {
357                service = (WFService) dataSource.getOGCWebService();
358            } catch ( OGCWebServiceException ogcwe ) {
359                LOG.logError( ogcwe.getMessage() );
360                // throw new RuntimeException( ogcwe );
361            }
362            if ( service == null ) {
363                LOG.logError( "No Web Feature Service instance available for creation of the Global height map." );
364            } else {
365                Object response = null;
366                try {
367    
368                    // create the GetFeature request.
369                    QualifiedName qn = dataSource.getName();
370    
371                    StringBuilder sb = new StringBuilder( 5000 );
372                    sb.append( "<?xml version='1.0' encoding='" + CharsetUtils.getSystemCharset() + "'?>" );
373                    sb.append( "<wfs:GetFeature xmlns:wfs='http://www.opengis.net/wfs' " );
374                    sb.append( "xmlns:ogc='http://www.opengis.net/ogc' " );
375                    sb.append( "xmlns:gml='http://www.opengis.net/gml' " );
376                    sb.append( "xmlns:" ).append( qn.getPrefix() ).append( '=' );
377                    sb.append( "'" ).append( qn.getNamespace() ).append( "' " );
378    
379                    if ( dataSource.getServiceType() == AbstractDataSource.LOCAL_WFS ) {
380                        sb.append( "outputFormat='FEATURECOLLECTION'>" );
381                    } else {
382                        sb.append( "outputFormat='text/xml; subtype=gml/3.1.1'>" );
383                    }
384    
385                    /**
386                     * To make things a little clearer compare with this SQL-Statement: SELECT ( !texture )? geoProperty : *
387                     * FROM qn.getLocalName() WHERE geoPoperty intersects with resolutionStripe.getSurface() AND
388                     * FilterConditions.
389                     */
390                    PropertyPath geoProperty = dataSource.getGeometryProperty();
391    
392                    // FROM
393                    sb.append( "<wfs:Query typeName='" ).append( qn.getPrefix() ).append( ":" );
394                    sb.append( qn.getLocalName() ).append( "'>" );
395    
396                    // SELECT
397                    StringBuffer sbArea = GMLGeometryAdapter.exportAsEnvelope( env );
398    
399                    // WHERE
400                    sb.append( "<ogc:Filter>" );
401    
402                    // AND
403                    Filter filter = dataSource.getFilter();
404                    if ( filter != null ) {
405                        if ( filter instanceof ComplexFilter ) {
406                            sb.append( "<ogc:And>" );
407                            sb.append( "<ogc:Intersects>" );
408                            sb.append( "<wfs:PropertyName>" );
409                            sb.append( geoProperty.getAsString() );
410                            sb.append( "</wfs:PropertyName>" );
411                            sb.append( sbArea );
412                            sb.append( "</ogc:Intersects>" );
413                            // add filter as defined in the layers datasource description
414                            // to the filter expression
415                            org.deegree.model.filterencoding.Operation op = ( (ComplexFilter) filter ).getOperation();
416                            sb.append( op.toXML() ).append( "</ogc:And>" );
417                        } else {
418                            if ( filter instanceof FeatureFilter ) {
419                                ArrayList<FeatureId> featureIds = ( (FeatureFilter) filter ).getFeatureIds();
420                                if ( featureIds.size() != 0 )
421                                    sb.append( "<ogc:And>" );
422                                for ( FeatureId fid : featureIds ) {
423                                    sb.append( fid.toXML() );
424                                }
425                                if ( featureIds.size() != 0 )
426                                    sb.append( "</ogc:And>" );
427                            }
428                        }
429                    } else {
430                        sb.append( "<ogc:Intersects>" );
431                        sb.append( "<wfs:PropertyName>" );
432                        sb.append( geoProperty.getAsString() );
433                        sb.append( "</wfs:PropertyName>" );
434                        sb.append( sbArea );
435                        sb.append( "</ogc:Intersects>" );
436                    }
437    
438                    sb.append( "</ogc:Filter></wfs:Query></wfs:GetFeature>" );
439    
440                    Document doc = null;
441                    try {
442                        doc = XMLTools.parse( new StringReader( sb.toString() ) );
443                    } catch ( Exception e ) {
444                        LOG.logError( e.getMessage(), e );
445                        // throw new OGCWebServiceException( e.getMessage() );
446                    }
447                    if ( doc != null ) {
448                        IDGenerator idg = IDGenerator.getInstance();
449                        GetFeature getFeature = GetFeature.create( String.valueOf( idg.generateUniqueID() ),
450                                                                   doc.getDocumentElement() );
451                        LOG.logDebug( "WFS request: " + getFeature );
452    
453                        // send the request
454                        response = service.doService( getFeature );
455                    }
456                } catch ( OGCWebServiceException ogcwse ) {
457                    if ( !Thread.currentThread().isInterrupted() ) {
458                        LOG.logError( "Exception while performing WFS-GetFeature: ", ogcwse );
459                    }
460                }
461    
462                if ( response != null && response instanceof FeatureResult ) {
463                    FeatureCollection fc = (FeatureCollection) ( (FeatureResult) response ).getResponse();
464                    if ( fc != null ) {
465                        PointsToPointListFactory ptpFac = new PointsToPointListFactory();
466                        List<Point3d> heights = ptpFac.createFromFeatureCollection( fc );
467    
468                        // find the min value.
469                        float min = Float.MAX_VALUE;
470                        for ( Point3d p : heights ) {
471                            min = Math.min( min, (float) p.z );
472                        }
473    
474                        result = new float[requestHeight][requestWidth];
475                        for ( float[] t : result ) {
476                            Arrays.fill( t, min );
477                        }
478    
479                        double scaleX = requestWidth / env.getWidth();
480                        double scaleY = requestHeight / env.getHeight();
481                        for ( Point3d height : heights ) {
482                            int x = (int) Math.round( ( height.x - env.getMin().getX() ) * scaleX );
483                            int y = (int) Math.round( requestHeight - ( ( height.y - env.getMin().getY() ) * scaleY ) );
484                            float savedHeight = result[y][x];
485                            if ( Math.abs( savedHeight - min ) > 1E-10 ) {
486                                savedHeight += height.z;
487                                result[y][x] = savedHeight * 0.5f;
488                            } else {
489                                result[y][x] = (float) height.z;
490                            }
491                        }
492                    }
493                } else {
494                    LOG.logError( "ERROR creating a global heightmap while invoking wfs-datasource : "
495                                  + dataSource.getName() + " the result was no WFS-response or no FeatureResult instance" );
496                }
497            }
498            return result;
499        }
500    
501        /**
502         * @param pos
503         *            the position to get the height value
504         * @return the height value of the given position or the globalMinimalHeight value if the position was outside the
505         *         heightmap.
506         */
507        public static double getHeightForPosition( Point3d pos ) {
508            int posX = (int) Math.floor( ( pos.x - configuredBBox.getMin().getX() ) * scaleMapWidth );
509            int posY = (int) Math.floor( mapHeight - ( ( pos.y - configuredBBox.getMin().getY() ) * scaleMapHeight ) );
510            if ( posY < 0 || posY > heightMap.length || posX < 0 || posX > heightMap[0].length ) {
511                LOG.logDebug( "Given position " + pos + " is outside the global height lookup, returning minimal value: "
512                              + globalMinimalHeight );
513                return globalMinimalHeight;
514            }
515            LOG.logDebug( "The looked up value for postion: " + pos + " (mapped to: " + posX + ", " + posY + ") is: "
516                          + heightMap[posY][posX] );
517            return heightMap[posY][posX];
518        }
519    
520        /**
521         * @param dataSource
522         * @param env
523         * @return the heightmap created from the wcs elevation model or an empty array if no such heightmap could be
524         *         created.
525         */
526        private float[][] invokeWCS( LocalWCSDataSource dataSource, Envelope env, int requestWidth, int requestHeight ) {
527            float[][] result = new float[0][0];
528            WCService service = null;
529            try {
530                service = (WCService) dataSource.getOGCWebService();
531            } catch ( OGCWebServiceException e1 ) {
532                e1.printStackTrace();
533            }
534    
535            if ( service == null ) {
536                LOG.logError( "No Web Coverage Service instance available for creation of the Global height map." );
537            } else {
538    
539                Object coverageResponse = null;
540    
541                try {
542                    String crsString = env.getCoordinateSystem().getFormattedString();
543                    Output output = GetCoverage.createOutput( crsString, null, dataSource.getDefaultFormat(), null );
544    
545                    // put missing parts in this map:
546                    Map<String, String> map = new HashMap<String, String>( 5 );
547                    StringBuffer sb = new StringBuffer( 1000 );
548                    Position p = env.getMin();
549                    sb.append( p.getX() ).append( "," ).append( p.getY() ).append( "," );
550                    p = env.getMax();
551                    sb.append( p.getX() ).append( "," ).append( p.getY() );
552                    map.put( "BBOX", sb.toString() );
553    
554                    map.put( "WIDTH", String.valueOf( requestWidth ) );
555                    map.put( "HEIGHT", String.valueOf( requestHeight ) );
556    
557                    SpatialSubset sps = GetCoverage.createSpatialSubset( map, crsString );
558    
559                    GetCoverage filterCondition = dataSource.getCoverageFilterCondition();
560    
561                    Code code = filterCondition.getDomainSubset().getRequestSRS();
562                    DomainSubset domainSubset = new DomainSubset( code, sps, null );
563    
564                    IDGenerator idg = IDGenerator.getInstance();
565    
566                    GetCoverage getCoverageRequest = new GetCoverage( String.valueOf( idg.generateUniqueID() ),
567                                                                      filterCondition.getVersion(),
568                                                                      filterCondition.getSourceCoverage(), domainSubset,
569                                                                      null, filterCondition.getInterpolationMethod(),
570                                                                      output );
571                    LOG.logDebug( "Sending wcs request:" + dataSource.getName() );
572                    coverageResponse = service.doService( getCoverageRequest );
573                } catch ( WCSException wcse ) {
574                    if ( !Thread.currentThread().isInterrupted() ) {
575                        LOG.logError( Messages.getMessage( "WPVS_WCS_REQUEST_ERROR", "WCSException", dataSource.getName(),
576                                                           wcse.getMessage() ) );
577                    }
578                } catch ( OGCWebServiceException ogcwse ) {
579                    if ( !Thread.currentThread().isInterrupted() ) {
580                        LOG.logError( Messages.getMessage( "WPVS_WCS_REQUEST_ERROR", "OGCWebServiceException",
581                                                           dataSource.getName(), ogcwse.getMessage() ) );
582                    }
583                } catch ( Throwable t ) {
584                    if ( !Thread.currentThread().isInterrupted() ) {
585                        t.printStackTrace();
586                    }
587                }
588                if ( coverageResponse != null && coverageResponse instanceof ResultCoverage ) {
589    
590                    LOG.logDebug( "\t -> a valid response\n" );
591                    ResultCoverage response = (ResultCoverage) coverageResponse;
592                    if ( response.getCoverage() != null && response.getCoverage() instanceof ImageGridCoverage ) {
593                        ImageGridCoverage igc = (ImageGridCoverage) response.getCoverage();
594                        BufferedImage image = igc.getAsImage( requestWidth, requestHeight );
595                        // the heightdata is in x and -y coordinates, they must be flipped before using
596                        // PlanarImage im2 = JAI.create( "transpose", image, TransposeDescriptor.FLIP_VERTICAL );
597                        if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
598                            try {
599                                File f = File.createTempFile( "global_wcs_dgm_response", ".jpg" );
600                                LOG.logDebug( "creating tmpfile for global wcs elevationmodel response with name: "
601                                              + f.toString() );
602                                f.deleteOnExit();
603                                ImageIO.write( image, "jpg", f );
604                            } catch ( Exception e ) {
605                                LOG.logError( e.getMessage(), e );
606                            }
607                        }
608                        // BufferedImage heightMap = image.get;
609                        Image2RawData i2rd = new Image2RawData( image, 1 );
610                        result = i2rd.parse();
611                    }
612                } else {
613                    LOG.logWarning( Messages.getMessage( "WPVS_IVALID_WCS_RESPONSE", dataSource.getName(),
614                                                         "an ImageGridCoverage" ) );
615                }
616            }
617            return result;
618        }
619    
620        /**
621         * @return an elevation datasource with the largest min scale denominator or <code>null</code> if no elevation model
622         *         datasource was found.
623         */
624        private AbstractDataSource findLargestElevModel() {
625            return findLargestElevModel( super.getDataset() );
626        }
627    
628        /**
629         * @param dataset
630         *            to look for the elevationmodel datasources.
631         * @return an elevation datasource with the largest min scale denominator or <code>null</code> if no elevation model
632         *         datasource was found.
633         */
634        private AbstractDataSource findLargestElevModel( Dataset dataset ) {
635            Dataset[] children = dataset.getDatasets();
636            ElevationModel model = dataset.getElevationModel();
637            AbstractDataSource elevSource = null;
638            if ( model != null ) {
639                AbstractDataSource[] sources = model.getDataSources();
640                if ( sources != null ) {
641                    for ( AbstractDataSource source : sources ) {
642                        if ( source != null
643                             && ( elevSource == null || source.getMinScaleDenominator() > elevSource.getMinScaleDenominator() ) ) {
644                            elevSource = source;
645                        }
646                    }
647                }
648            }
649            for ( Dataset set : children ) {
650                AbstractDataSource tmpSource = findLargestElevModel( set );
651                if ( tmpSource != null
652                     && ( elevSource == null || tmpSource.getMinScaleDenominator() > elevSource.getMinScaleDenominator() ) ) {
653                    elevSource = tmpSource;
654                }
655            }
656            return elevSource;
657        }
658    
659        /**
660         * @return an Offscreen canvas3D from a simple pool if it has no view and the renderer is not running.
661         */
662        public synchronized static Canvas3D getCanvas3D() {
663            LOG.logDebug( "The pool now contains: " + canvasPool.size() + " canvasses" );
664            LOG.logDebug( "The inuse pool now contains: " + inUseCanvases.size() + " canvasses" );
665            for ( Canvas3D c : canvasPool ) {
666                if ( !inUseCanvases.contains( c ) ) {
667                    if ( c != null ) {
668                        View v = c.getView();
669                        LOG.logDebug( "Canvas has view attached: " + v );
670                        if ( v != null ) {
671                            LOG.logDebug( "Removing the view from the pooled Canvas3D because it is not in use anymore." );
672                            v.removeAllCanvas3Ds();
673                        }
674                        LOG.logDebug( "Using a pooled Canvas3D." );
675                        inUseCanvases.add( c );
676                        return c;
677                    }
678                }
679            }
680            LOG.logDebug( "Creating a new Canvas3D, because all canvasses are in use." );
681            Canvas3D tmp = createOffscreenCanvas3D();
682            canvasPool.add( tmp );
683            inUseCanvases.add( tmp );
684            return tmp;
685        }
686    
687        /**
688         * @param canvas
689         *            to be released.
690         * @return true if the removal operation was successful, false if the given canvas3D was not in the list of used
691         *         canvasses.
692         */
693        public synchronized static boolean releaseCanvas3D( Canvas3D canvas ) {
694            if ( canvas != null ) {
695                View v = canvas.getView();
696                if ( v != null ) {
697                    LOG.logDebug( "Removing the view from the Canvas3D because it is not used anymore." );
698                    v.removeAllCanvas3Ds();
699                }
700                if ( inUseCanvases.contains( canvas ) ) {
701                    LOG.logDebug( "Removing the given Canvas3D from the list." );
702    
703                    return inUseCanvases.remove( canvas );
704                }
705                LOG.logInfo( "The given canvas3D was not held by the configuration." );
706            }
707            LOG.logDebug( "The pool now contains: " + canvasPool.size() + " canvasses" );
708            LOG.logDebug( "The inuse pool now contains: " + inUseCanvases.size() + " canvasses" );
709            return false;
710        }
711    
712        /**
713         * creates and returns a canvas for offscreen rendering
714         * 
715         * @return a offscreen Canvas3D on which the the scene will be rendered.
716         */
717        @SuppressWarnings("unchecked")
718        protected static synchronized Canvas3D createOffscreenCanvas3D() {
719            GraphicsDevice[] gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices();
720            GraphicsConfigTemplate3D gc3D = new GraphicsConfigTemplate3D();
721            gc3D.setSceneAntialiasing( GraphicsConfigTemplate.PREFERRED );
722            gc3D.setDoubleBuffer( GraphicsConfigTemplate.REQUIRED );
723    
724            if ( gd != null && gd.length > 0 ) {
725                GraphicsConfiguration gc = gd[0].getBestConfiguration( gc3D );
726                if ( gc != null ) {
727                    Canvas3D offScreenCanvas3D = new Canvas3D( gc, true );
728                    Map<String, ?> props = offScreenCanvas3D.queryProperties();
729                    if ( props.containsKey( tusm ) ) {
730                        Integer tus = (Integer) props.get( tusm );
731                        if ( tus != null ) {
732                            availableTextureUnitStates = tus.intValue();
733                        }
734                    }
735    
736                    if ( props.containsKey( twm ) ) {
737                        Integer tw = (Integer) props.get( twm );
738                        if ( tw != null ) {
739                            texture2DMaxSize = tw.intValue();
740                        }
741                    }
742    
743                    if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
744                        Set<String> keys = props.keySet();
745                        StringBuilder sb = new StringBuilder( "Canvas3D has following properties:\n" );
746                        for ( String key : keys ) {
747                            sb.append( key ).append( " : " ).append( props.get( key ) ).append( "\n" );
748                        }
749                        LOG.logDebug( sb.toString() );
750                    }
751                    return offScreenCanvas3D;
752                }
753                LOG.logError( "Could not get a GraphicsConfiguration from the graphics environment, cannot create a canvas3d." );
754            } else {
755                LOG.logError( "Could not get a graphicsdevice to create a canvas3d." );
756            }
757            return null;
758        }
759    
760        /**
761         * @return Returns the deegreeParams.
762         */
763        public WPVSDeegreeParams getDeegreeParams() {
764            return deegreeParams;
765        }
766    
767        /**
768         * @param deegreeParams
769         *            The deegreeParams to set.
770         */
771        public void setDeegreeParams( WPVSDeegreeParams deegreeParams ) {
772            this.deegreeParams = deegreeParams;
773        }
774    
775        /**
776         * @return the smallestMinimalScaleDenomiator value.
777         */
778        public double getSmallestMinimalScaleDenomiator() {
779            return smallestMinimalScaleDenomiator;
780        }
781    }