001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/ogcwebservices/wpvs/DefaultGetViewHandler.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;
038
039 import java.awt.Color;
040 import java.awt.Font;
041 import java.awt.Graphics;
042 import java.awt.Graphics2D;
043 import java.awt.RenderingHints;
044 import java.awt.geom.AffineTransform;
045 import java.awt.image.BufferedImage;
046 import java.io.File;
047 import java.io.IOException;
048 import java.net.URL;
049 import java.util.ArrayList;
050 import java.util.Calendar;
051 import java.util.List;
052 import java.util.concurrent.Callable;
053 import java.util.concurrent.CancellationException;
054
055 import javax.imageio.ImageIO;
056 import javax.media.j3d.Background;
057 import javax.media.j3d.BoundingSphere;
058 import javax.media.j3d.Canvas3D;
059 import javax.media.j3d.ImageComponent2D;
060 import javax.media.j3d.Node;
061 import javax.media.j3d.OrderedGroup;
062 import javax.vecmath.Color3f;
063 import javax.vecmath.Point3d;
064
065 import org.deegree.framework.concurrent.ExecutionFinishedEvent;
066 import org.deegree.framework.concurrent.Executor;
067 import org.deegree.framework.log.ILogger;
068 import org.deegree.framework.log.LoggerFactory;
069 import org.deegree.framework.util.StringTools;
070 import org.deegree.framework.util.TimeTools;
071 import org.deegree.i18n.Messages;
072 import org.deegree.model.crs.CRSFactory;
073 import org.deegree.model.crs.CRSTransformationException;
074 import org.deegree.model.crs.CoordinateSystem;
075 import org.deegree.model.crs.GeoTransformer;
076 import org.deegree.model.crs.UnknownCRSException;
077 import org.deegree.model.spatialschema.Envelope;
078 import org.deegree.model.spatialschema.GeometryException;
079 import org.deegree.model.spatialschema.Surface;
080 import org.deegree.model.spatialschema.WKTAdapter;
081 import org.deegree.ogcbase.ExceptionCode;
082 import org.deegree.ogcwebservices.InconsistentRequestException;
083 import org.deegree.ogcwebservices.OGCWebServiceException;
084 import org.deegree.ogcwebservices.wpvs.capabilities.Dataset;
085 import org.deegree.ogcwebservices.wpvs.capabilities.ElevationModel;
086 import org.deegree.ogcwebservices.wpvs.configuration.AbstractDataSource;
087 import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration;
088 import org.deegree.ogcwebservices.wpvs.configuration.WPVSDeegreeParams;
089 import org.deegree.ogcwebservices.wpvs.j3d.OffScreenWPVSRenderer;
090 import org.deegree.ogcwebservices.wpvs.j3d.TriangleTerrain;
091 import org.deegree.ogcwebservices.wpvs.j3d.ViewPoint;
092 import org.deegree.ogcwebservices.wpvs.j3d.WPVSScene;
093 import org.deegree.ogcwebservices.wpvs.operation.GetView;
094 import org.deegree.ogcwebservices.wpvs.operation.GetViewResponse;
095 import org.deegree.ogcwebservices.wpvs.utils.QuadTreeSplitter;
096 import org.deegree.ogcwebservices.wpvs.utils.ResolutionStripe;
097 import org.deegree.ogcwebservices.wpvs.utils.StripeFactory;
098
099 import com.sun.j3d.utils.image.TextureLoader;
100
101 /**
102 * Default handler for WPVS GetView requests. This Class is the central position where the {@link GetView} request lands
103 * the configured Datasources are gathered and the scene is put together.
104 *
105 * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
106 * @author last edited by: $Author: rbezema $
107 *
108 * $Revision: 19665 $, $Date: 2009-09-16 10:11:29 +0200 (Mi, 16. Sep 2009) $
109 *
110 */
111 public class DefaultGetViewHandler extends GetViewHandler {
112
113 static private final ILogger LOG = LoggerFactory.getLogger( DefaultGetViewHandler.class );
114
115 private WPVSConfiguration config;
116
117 private URL backgroundImgURL;
118
119 private WPVSScene theScene;
120
121 private OffScreenWPVSRenderer renderer;
122
123 /**
124 * Constructor for DefaultGetViewHandler.
125 *
126 * @param owner
127 * the service creating this handler
128 */
129 public DefaultGetViewHandler( WPVService owner ) {
130 super( owner );
131 this.config = owner.getConfiguration();
132 }
133
134 /**
135 * This Method handles a clients GetView request by creating the appropriate (configured) Datasources, the
136 * {@link ResolutionStripe}s, the requeststripes (which are actually axisalligned Resolutionsripes) and finally
137 * putting them all together in a java3d scene. The creation of the Shape3D Objects (by requesting the
138 * ResolutionStripe relevant Datasources) is done in separate Threads for each ResolutionStripe (which is in
139 * conflict with the deegree styleguides) using the {@link Executor} class.
140 *
141 * @see org.deegree.ogcwebservices.wpvs.GetViewHandler#handleRequest(org.deegree.ogcwebservices.wpvs.operation.GetView)
142 */
143 @Override
144 public GetViewResponse handleRequest( final GetView request )
145 throws OGCWebServiceException {
146
147 // request = req;
148 long totalTime = System.currentTimeMillis();
149 validateImageSize( request );
150
151 List<Dataset> validDatasets = new ArrayList<Dataset>();
152 String errorMessage = getValidRequestDatasets( request, validDatasets );
153 if ( validDatasets.size() == 0 ) {
154 throw new OGCWebServiceException(
155 StringTools.concat(
156 errorMessage.length() + 100,
157 "Your request yields no results for given reasons:\n",
158 errorMessage ) );
159
160 }
161
162 ElevationModel elevationModel = getValidElevationModel( request.getElevationModel() );
163 LOG.logDebug( "Requested elevationModel: " + elevationModel );
164
165 if ( request.getFarClippingPlane() > config.getDeegreeParams().getMaximumFarClippingPlane() ) {
166 request.setFarClippingPlane( config.getDeegreeParams().getMaximumFarClippingPlane() );
167 }
168
169 ViewPoint viewPoint = new ViewPoint( request, getTerrainHeightAboveSeaLevel( request.getPointOfInterest() ) );
170 LOG.logDebug( "Viewpoint: " + viewPoint );
171
172 ArrayList<ResolutionStripe> resolutionStripes = createRequestBoxes( request, viewPoint,
173 config.getSmallestMinimalScaleDenomiator() );
174 LOG.logDebug( "Found number of resolutionStripes: " + resolutionStripes.size() );
175
176 if ( resolutionStripes.size() == 0 ) {
177 throw new OGCWebServiceException(
178 StringTools.concat( 200,
179 "There were no RequestBoxes found, therefor this WPVS-request is invalid" ) );
180
181 }
182
183 Surface visibleArea = viewPoint.getVisibleArea();
184
185 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
186 try {
187 LOG.logDebug( "Visible Area:\n" + WKTAdapter.export( visibleArea ).toString() );
188 } catch ( GeometryException e1 ) {
189 // TODO Auto-generated catch block
190 e1.printStackTrace();
191 }
192 }
193
194 findValidDataSourcesFromDatasets( validDatasets, resolutionStripes, request.getOutputFormat(), visibleArea );
195 LOG.logDebug( "Using validDatasets: " + validDatasets );
196
197 findValidEMDataSourceFromElevationModel( elevationModel, resolutionStripes, visibleArea );
198 LOG.logDebug( "Using elevationModel: " + elevationModel );
199
200 // will also check is background is valid, before doing any hard work
201 this.backgroundImgURL = createBackgroundImageURL( request );
202
203 LOG.logDebug( "Backgroundurl : " + backgroundImgURL );
204
205 Executor exec = Executor.getInstance();
206 List<ExecutionFinishedEvent<ResolutionStripe>> resultingBoxes = null;
207 try {
208 LOG.logDebug( "Trying to synchronously contact datasources." );
209 resultingBoxes = exec.performSynchronously( new ArrayList<Callable<ResolutionStripe>>( resolutionStripes ),
210 config.getDeegreeParams().getRequestTimeLimit() );
211 } catch ( InterruptedException ie ) {
212 throw new OGCWebServiceException(
213 StringTools.concat( 200,
214 "A Threading-Error occurred while placing your request." ) );
215
216 }
217
218 LOG.logDebug( "All resolutionstripes finished executing." );
219
220 if ( resultingBoxes != null && resultingBoxes.size() > 0 ) {
221 // check if some of the resolutionstripes finished execution before the configured max
222 // life time.
223 int k = 0;
224 for ( ExecutionFinishedEvent<ResolutionStripe> efe : resultingBoxes ) {
225 try {
226 efe.getResult();
227 } catch ( CancellationException ce ) {
228 k++;
229 } catch ( Throwable e ) {
230 // not interested in something else
231 }
232 }
233 if ( k == resultingBoxes.size() ) {
234 throw new OGCWebServiceException(
235 Messages.getMessage(
236 "WPVS_EXCEEDED_REQUEST_TIME",
237 new Double(
238 config.getDeegreeParams().getRequestTimeLimit() * 0.001 ) ).toString() );
239 }
240 double dataRetrievalTime = ( System.currentTimeMillis() - totalTime ) / 1000d;
241 long creationTime = System.currentTimeMillis();
242 theScene = createScene( request, viewPoint, resolutionStripes, visibleArea.getEnvelope() );
243 // Get a canvas3D from the pool.
244 Canvas3D canvas = WPVSConfiguration.getCanvas3D();
245 renderer = new OffScreenWPVSRenderer( canvas, config.getDeegreeParams().getNearClippingPlane(), theScene,
246 request.getImageDimension().width,
247 request.getImageDimension().height,
248 config.getDeegreeParams().isAntialiasingEnabled() );
249 double creationT = ( ( System.currentTimeMillis() - creationTime ) / 1000d );
250 LOG.logDebug( "Trying to render Scene." );
251
252 long renderTime = System.currentTimeMillis();
253 BufferedImage output = renderScene();
254 // Release the canvas so it may be reused by another request.
255 /**
256 * If using the TestWPVS class option NO_NEW_REQUEST, make sure to comment following line as well as the
257 * universe.cleanup(); and view.removeAllCanvas3Ds(); lines in the
258 * {@link OffscreenWPVSRenderer#renderScene()}.
259 */
260 WPVSConfiguration.releaseCanvas3D( canvas );
261 double renderT = ( ( System.currentTimeMillis() - renderTime ) / 1000d );
262
263 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
264 StringBuilder sb = new StringBuilder(
265 "\n-----------TIMING INFORMATION----------\nThe handling of the request took: " );
266 sb.append( ( System.currentTimeMillis() - totalTime ) / 1000d );
267 sb.append( " seconds to return." );
268 sb.append( "\n- Retreiving data took: " ).append( dataRetrievalTime ).append( "seconds." );
269 sb.append( "\n- Creating j3d scene took: " ).append( creationT ).append( "seconds." );
270 sb.append( "\n- Rendering the j3d scene took: " ).append( renderT ).append( "seconds." );
271 sb.append( "\n--------------------------------------\n\n Outputting all retreived textures for " ).append(
272 resolutionStripes.size() ).append(
273 " finished resolutionsSripes." );
274 LOG.logDebug( sb.toString() );
275 for ( ResolutionStripe stripe : resolutionStripes ) {
276 stripe.outputTextures();
277 }
278 LOG.logDebug( "Number of resolutionStripes: " + resolutionStripes.size() );
279 }
280
281 return new GetViewResponse( output, request.getOutputFormat() );
282 }
283 return null;
284 }
285
286 /**
287 * @param elevationModelName
288 * @return an elevationModell or <code>null</code> if no name was given
289 * @throws OGCWebServiceException
290 * if no elevationModel is found for the given elevationmodelname
291 */
292 private ElevationModel getValidElevationModel( String elevationModelName )
293 throws OGCWebServiceException {
294 ElevationModel resultEMModel = null;
295 if ( elevationModelName != null ) {
296 resultEMModel = config.findElevationModel( elevationModelName );// dataset.getElevationModel();
297 if ( resultEMModel == null ) {
298 throw new OGCWebServiceException( StringTools.concat( 150, "ElevationModel '", elevationModelName,
299 "' is not known to the WPVS" ) );
300 }
301 }
302 return resultEMModel;
303 }
304
305 /**
306 * Finds the datasets which can handle the requested crs's, and check if they are defined inside the requested bbox
307 *
308 * @param request
309 * the GetView request
310 * @param resultDatasets
311 * a list to which the found datasets will be added.
312 * @return an ArrayList containing all the datasets which comply with the requested crs.
313 * @throws OGCWebServiceException
314 */
315 private String getValidRequestDatasets( GetView request, List<Dataset> resultDatasets )
316 throws OGCWebServiceException {
317 // ArrayList<Dataset> resultDatasets = new ArrayList<Dataset>();
318 Envelope bbox = request.getBoundingBox();
319 List<String> datasets = request.getDatasets();
320 CoordinateSystem coordSys = request.getCrs();
321
322 // If the BoundingBox request not is
323 try {
324 if ( !"EPSG:4326".equalsIgnoreCase( coordSys.getFormattedString() ) ) {
325 // transform the bounding box of the request to EPSG:4326/WGS 84
326 GeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
327 bbox = gt.transform( bbox, coordSys );
328 }
329 } catch ( CRSTransformationException e ) {
330 LOG.logError( e.getMessage(), e );
331 throw new OGCWebServiceException( e.getMessage() );
332 } catch ( UnknownCRSException e ) {
333 LOG.logError( e.getMessage(), e );
334 throw new OGCWebServiceException( e.getMessage() );
335 }
336 StringBuffer errorMessage = new StringBuffer( 1000 );
337 for ( String dset : datasets ) {
338 Dataset configuredDataset = config.findDataset( dset );
339 if ( configuredDataset != null ) {
340 CoordinateSystem[] dataSetCRS = configuredDataset.getCrs();
341 boolean isCoordSysSupported = false;
342 boolean requestIntersectsWithDataset = false;
343 for ( CoordinateSystem crs : dataSetCRS ) {
344 if ( crs.equals( coordSys ) ) {
345 isCoordSysSupported = true;
346 // lookslike compatible crs therefor check if bbox intersect
347 if ( configuredDataset.getWgs84BoundingBox().intersects( bbox ) ) {
348 requestIntersectsWithDataset = true;
349 if ( !resultDatasets.contains( configuredDataset ) ) {
350 resultDatasets.add( configuredDataset );
351 }
352 }
353 }
354 }
355 if ( !isCoordSysSupported ) {
356 String msg = new StringBuilder( "Requested Dataset -" ).append( dset ).append(
357 "- does not support requested crs: " ).append(
358 coordSys ).append(
359 ".\n" ).toString();
360 LOG.logDebug( msg );
361 errorMessage.append( msg );
362 } else if ( !requestIntersectsWithDataset ) {
363 String msg = new StringBuilder( "Requested Dataset -" ).append( dset ).append(
364 "- does not intersect with the requested bbox.\n" ).toString();
365 LOG.logDebug( msg );
366 errorMessage.append( msg );
367 }
368 } else {
369 throw new InconsistentRequestException( Messages.getMessage( "WPVS_GETVIEW_INVALID_DATASET", dset ) );
370 }
371 }
372
373 return errorMessage.toString();
374 }
375
376 /**
377 * Finds the valid datasources in the configured (and allready checked) datasets.
378 *
379 * @param datasets
380 * the datasets which are valid for this request
381 */
382 private void findValidDataSourcesFromDatasets( List<Dataset> datasets, ArrayList<ResolutionStripe> stripes,
383 String outputFormat, Surface visibleArea ) {
384 double resolution = 0;
385 for ( ResolutionStripe stripe : stripes ) {
386 stripe.setOutputFormat( outputFormat );
387 resolution = stripe.getMaxResolutionAsScaleDenominator();
388 for ( int i = 0; i < datasets.size(); ++i ) {
389 Dataset dset = datasets.get( i );
390 AbstractDataSource[] dataSources = dset.getDataSources();
391
392 for ( AbstractDataSource ads : dataSources ) {
393 // System.out.println( "AbstractDataSource: " + ads );
394 if ( resolution >= dset.getMinimumScaleDenominator()
395 && resolution < dset.getMaximumScaleDenominator()
396 && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
397 && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
398 if ( ads.getServiceType() == AbstractDataSource.LOCAL_WFS
399 || ads.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
400 stripe.addFeatureCollectionDataSource( ads );
401 } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS
402 || ads.getServiceType() == AbstractDataSource.REMOTE_WMS
403 || ads.getServiceType() == AbstractDataSource.LOCAL_WCS
404 || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
405 stripe.addTextureDataSource( ads );
406 }
407 }
408 }
409 }
410 }
411 }
412
413 /**
414 * @param elevationModel
415 * and it's datasources.
416 */
417 private void findValidEMDataSourceFromElevationModel( ElevationModel elevationModel,
418 ArrayList<ResolutionStripe> stripes, Surface visibleArea ) {
419 if ( elevationModel != null ) {
420 AbstractDataSource[] emDataSources = elevationModel.getDataSources();
421 Dataset dataset = elevationModel.getParentDataset();
422 for ( ResolutionStripe stripe : stripes ) {
423 double resolution = stripe.getMaxResolutionAsScaleDenominator();// getMaxResolution();
424 for ( AbstractDataSource ads : emDataSources ) {
425 if ( resolution >= dataset.getMinimumScaleDenominator()
426 && resolution < dataset.getMaximumScaleDenominator()
427 && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
428 && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
429 stripe.setElevationModelDataSource( ads );
430 }
431 }
432 }
433 }
434 }
435
436 /**
437 * Extracts from the request and the configuration the URL behind teh name of a given BACKGROUND
438 *
439 * @return the URL, under which the background image is found
440 * @throws OGCWebServiceException
441 * if no URL with the name given by 'BACKGROUND' can be found.
442 */
443 private URL createBackgroundImageURL( GetView request )
444 throws OGCWebServiceException {
445
446 String imageName = request.getVendorSpecificParameter( "BACKGROUND" );
447 URL imgURL = null;
448 if ( imageName != null ) {
449 imgURL = config.getDeegreeParams().getBackgroundMap().get( imageName );
450 if ( imgURL == null ) {
451 String s = StringTools.concat( 100, "Cannot find any image referenced", "by parameter BACKGROUND=",
452 imageName );
453 throw new OGCWebServiceException( s );
454 }
455 }
456
457 return imgURL;
458 }
459
460 /**
461 * Creates a Java3D Node representing the background.
462 *
463 * @param viewPoint
464 * the viewersposition
465 * @return a new Node containing a geometry representing the background
466 * @throws OGCWebServiceException
467 */
468 private Node createBackground( ViewPoint viewPoint, GetView request )
469 throws OGCWebServiceException {
470
471 Point3d observer = viewPoint.getObserverPosition();
472
473 Background bg = new Background( new Color3f( request.getBackgroundColor() ) );
474 BoundingSphere bounds = new BoundingSphere( observer,
475 config.getDeegreeParams().getMaximumFarClippingPlane() * 1.1 );
476
477 bg.setApplicationBounds( bounds );
478 try {
479 if ( backgroundImgURL != null ) {
480 BufferedImage buffImg = ImageIO.read( backgroundImgURL );
481
482 // scale image to fill the whole background
483 BufferedImage tmpImg = new BufferedImage( request.getImageDimension().width,
484 request.getImageDimension().height, buffImg.getType() );
485 Graphics g = tmpImg.getGraphics();
486 g.drawImage( buffImg, 0, 0, tmpImg.getWidth() - 1, tmpImg.getHeight() - 1, null );
487 g.dispose();
488
489 ImageComponent2D img = new TextureLoader( tmpImg ).getImage();
490 bg.setImage( img );
491 }
492 } catch ( IOException e ) {
493 LOG.logError( e.getMessage(), e );
494 String s = StringTools.concat( 100, "Could not create backgound image: ", e.getMessage() );
495 throw new OGCWebServiceException( s );
496 }
497
498 return bg;
499 }
500
501 /**
502 * Creates the request boxes from the parameters available i teh incoming request.
503 *
504 * @param viewPoint
505 * where the viewer is
506 * @return a new array of surfaces representing the area in which data will be collected
507 */
508 private ArrayList<ResolutionStripe> createRequestBoxes( GetView request, ViewPoint viewPoint,
509 double smallestMinimalScaleDenominator ) {
510 ArrayList<ResolutionStripe> requestStripes = new ArrayList<ResolutionStripe>();
511 String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
512 StripeFactory stripesFactory = new StripeFactory( viewPoint, smallestMinimalScaleDenominator );
513 int imageWidth = request.getImageDimension().width;
514 if ( "BBOX".equals( splittingMode ) ) {
515 requestStripes = stripesFactory.createBBoxResolutionStripe(
516 request.getBoundingBox(),
517 imageWidth,
518 getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
519 request.getScale() );
520 } else {
521 // Calculate the resolution stripes perpendicular to the viewdirection
522 requestStripes = stripesFactory.createResolutionStripes(
523 request.getImageDimension().width,
524 getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
525 null, request.getScale() );
526 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
527 StringBuilder sb = new StringBuilder( "The requestStripes (in WKT) before the quadtree: \n" );
528 for ( ResolutionStripe stripe : requestStripes ) {
529 try {
530 sb.append( WKTAdapter.export( stripe.getSurface() ) ).append( "\n" );
531 } catch ( GeometryException e ) {
532 LOG.logError( "Error while exporting surface to wkt.", e );
533 }
534 }
535 LOG.logDebug( sb.toString() );
536 }
537 QuadTreeSplitter splittree = new QuadTreeSplitter( requestStripes, request.getImageDimension().width,
538 config.getDeegreeParams().isRequestQualityPreferred() );
539 requestStripes = splittree.getRequestQuads( config.getDeegreeParams().getExtendRequestPercentage(),
540 config.getDeegreeParams().getQuadMergeCount() );
541 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
542 StringBuilder sb = new StringBuilder( "The requestStripes (in WKT) after the quadtree: \n" );
543 for ( ResolutionStripe stripe : requestStripes ) {
544 try {
545 sb.append( WKTAdapter.export( stripe.getSurface() ) ).append( "\n" );
546 } catch ( GeometryException e ) {
547 LOG.logError( "Error while exporting surface to wkt.", e );
548 }
549 }
550 LOG.logDebug( sb.toString() );
551 }
552 }
553 return requestStripes;
554 }
555
556 /**
557 * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
558 * configured MinimalTerrainHeight.
559 *
560 * @param eyePositionX
561 * to find the height of the sealevel for (not used yet)
562 * @param eyePositionY
563 * to find the height of the sealevel for (not used yet)
564 * @param eyePositionZ
565 * to find the height of the sealevel for (not used yet)
566 * @return the height above the seaLevel.
567 */
568 protected double getTerrainHeightAboveSeaLevel( double eyePositionX, double eyePositionY, double eyePositionZ ) {
569 return getTerrainHeightAboveSeaLevel( new Point3d( eyePositionX, eyePositionY, eyePositionZ ) );
570 }
571
572 /**
573 * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
574 * configured MinimalTerrainHeight.
575 *
576 * @param eyePosition
577 * to find the height of the sealevel for (not used yet)
578 *
579 * @return the height above the seaLevel.
580 */
581 protected double getTerrainHeightAboveSeaLevel( Point3d eyePosition ) {
582 return WPVSConfiguration.getHeightForPosition( eyePosition );
583 }
584
585 /**
586 * Creates a WPVS scene
587 *
588 * @param viewPoint
589 * position of the viewer
590 * @return a new scene
591 * @throws OGCWebServiceException
592 * if the background img cannot be read
593 */
594 private WPVSScene createScene( GetView request, ViewPoint viewPoint, List<ResolutionStripe> stripes,
595 Envelope sceneBBox )
596 throws OGCWebServiceException {
597 if ( stripes == null || stripes.size() == 0 ) {
598 String msg = "No resolutionStripes were given, therefore no scene can be created.";
599 LOG.logError( msg );
600 throw new OGCWebServiceException( msg );
601 }
602 LOG.logDebug( "Creating scene with " + stripes.size() + " number of resolution stripes " );
603 OrderedGroup scene = new OrderedGroup();
604 int dgmType = ResolutionStripe.ELEVATION_MODEL_UNKNOWN;
605 for ( int i = 0; i < stripes.size() && dgmType == ResolutionStripe.ELEVATION_MODEL_UNKNOWN; ++i ) {
606 dgmType = stripes.get( i ).getDGMType();
607 }
608 if ( dgmType == ResolutionStripe.ELEVATION_MODEL_POINTS ) {
609 LOG.logDebug( "The elevation model uses points, therefore generating one terrain." );
610
611 List<Point3d> terrainPoints = new ArrayList<Point3d>();
612 double sceneWidth = sceneBBox.getWidth();
613 double sceneHeight = sceneBBox.getHeight();
614 double sceneMinX = sceneBBox.getMin().getX();
615 double sceneMinY = sceneBBox.getMin().getY();
616
617 /*
618 * First find the resolution stripe with the best (==highest) resolution, and while doing so, add all
619 * measurepoints to the point array.
620 */
621 ResolutionStripe hasBestResolution = null;
622 for ( ResolutionStripe stripe : stripes ) {
623 if ( stripe.getMeassurepointsAsList() != null ) {
624 terrainPoints.addAll( stripe.getMeassurepointsAsList() );
625 }
626 if ( hasBestResolution == null || stripe.getMaxResolution() < hasBestResolution.getMaxResolution() ) {
627 if ( stripe.getRequestHeightForBBox() != -1 && stripe.getRequestWidthForBBox() != -1 ) {
628 hasBestResolution = stripe;
629 }
630 }
631
632 }
633 // Allthough it will probably never happen, it might be better to check.
634 if ( hasBestResolution == null ) {
635 LOG.logError( "No best resolutionStripe was found, this can happen if none of the stripes has a resolution or if the the requestwidths /heights of all returned -1 (e.g. were to large to be handled ) therefore no scene can be created." );
636 throw new OGCWebServiceException( "Could not create scene due to internal errors",
637 ExceptionCode.NOAPPLICABLECODE );
638 }
639
640 BufferedImage texture = hasBestResolution.getResultTexture();
641 double stripeTextureWidth = hasBestResolution.getRequestWidthForBBox();
642 double stripeTextureHeight = hasBestResolution.getRequestHeightForBBox();
643 // if an error occurred the texture might be null, in which case we use the textureWidth
644 // and height.
645 if ( texture == null ) {
646 LOG.logInfo( "The best ResolutionStripe has no texture (this normally means, an error occurred while invoking it's texture datasources) creating a new (empty) texture." );
647 texture = new BufferedImage( (int) stripeTextureWidth, (int) stripeTextureHeight,
648 BufferedImage.TYPE_INT_ARGB );
649 }
650
651 String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
652 if ( "BBOX".equals( splittingMode ) ) {
653 if ( stripes.size() != 1 ) {
654 LOG.logError( "Allthough the bbox splitter is used, we have more then one ResolutionStripe, this may not be, using only the first Stripe" );
655 }
656 LOG.logDebug( "The request is a bbox, just using the first (and only) resolution stripe's texture." );
657 sceneBBox = request.getBoundingBox();
658 } else {
659
660 /**
661 * The goal is to create one large textures to which all other textures are upscaled and then painted
662 * upon. To do so, first get the largest texture, which scale will be used to create an image (the
663 * resulttexture) which has the the dimensions of the scenes bbox. All that has to be done then, is to
664 * calculate for each the stripe, the relative position of it's bbox, to the scene's bbox and the scale
665 * of it's texture compared to the result texture's dimensions. This only has one little drawback, if
666 * the scene is large (a steep few for example) the texture can get larger as the available texture
667 * width of the gpu, in this case another scaling has to be applied.
668 */
669 Envelope stripeBBox = hasBestResolution.getSurface().getEnvelope();
670
671 // calculate the relation between the scene and the stripes bbox. and calculate the
672 // width and height of the resulting texture.
673 double scaleW = sceneWidth / stripeBBox.getWidth();
674 double scaleH = sceneHeight / stripeBBox.getHeight();
675 double resultTextureWidth = stripeTextureWidth * scaleW;
676 double resultTextureHeight = stripeTextureHeight * scaleH;
677
678 // Check the scale against the maximum texture size.
679 double scale = getTextureScale( resultTextureWidth, resultTextureHeight );
680 texture = new BufferedImage( (int) Math.floor( resultTextureWidth * scale ),
681 (int) Math.floor( resultTextureHeight * scale ), texture.getType() );
682
683 resultTextureHeight = texture.getHeight();
684 resultTextureWidth = texture.getWidth();
685
686 // Get the graphics object and set the hints to maximum quality.
687 Graphics2D g2d = (Graphics2D) texture.getGraphics();
688 /**
689 * We tried to set the rendering hints to different values here, but it seems, the standard values
690 * result in the quickest and best quality, because j3d will do antialiasing itself again.
691 */
692
693 // the transform will scale and translate the requested textures onto the one master
694 // texture.
695 AffineTransform origTransform = g2d.getTransform();
696
697 long paintTime = System.currentTimeMillis();
698 // now draw all available texture onto the result texture.
699
700 for ( ResolutionStripe stripe : stripes ) {
701 BufferedImage stripeTexture = stripe.getResultTexture();
702
703 if ( stripeTexture != null ) {
704
705 /**
706 * First calculate the position of this stripe's texture as if it was to be drawn upon it's own
707 * all fitting texture.
708 */
709 stripeBBox = stripe.getSurface().getEnvelope();
710 // find the offset of the stripes bbox to the scene bbox.
711 double realDistX = ( stripeBBox.getMin().getX() - sceneMinX ) / sceneWidth;
712 double realDistY = ( stripeBBox.getMin().getY() - sceneMinY ) / sceneHeight;
713
714 /**
715 * calculate the scale of the stripes' bbox to the scene's bbox
716 */
717 scaleW = sceneWidth / stripeBBox.getWidth();
718 scaleH = sceneHeight / stripeBBox.getHeight();
719 /**
720 * Calculate the virtual all-fitting-texture for this stripe
721 */
722 double tmpTextureWidth = stripeTexture.getWidth() * scaleW;
723 double tmpTextureHeight = stripeTexture.getHeight() * scaleH;
724
725 /**
726 * It might happen, that the viratula all-fitting-texture of the upscaled texture would have
727 * been to large for the texture memory to fit, therefore this scaling factor has to be taken
728 * into account as well.
729 */
730 double maxTextureScale = getTextureScale( resultTextureWidth, resultTextureHeight );
731
732 /**
733 * Calculate the scale of the virtual all-fitting-texture [with width
734 * =(tmpTextureWidth*maxTextureScale) and height= (tmpTextureHeight*maxTextureScale) ], to the
735 * real result texture.
736 */
737 double scaleX = resultTextureWidth / ( tmpTextureWidth * maxTextureScale );
738 double scaleY = resultTextureHeight / ( tmpTextureHeight * maxTextureScale );
739
740 /**
741 * And calculate the position within the virtual all-fitting-texture.
742 */
743 double posX = tmpTextureWidth * realDistX;
744 double posY = tmpTextureHeight
745 - ( ( tmpTextureHeight * realDistY ) + stripeTexture.getHeight() );
746 if ( posY < 0 ) {
747 posY = 0;
748 }
749 if ( posY > tmpTextureHeight ) {
750 posY = (int) Math.floor( tmpTextureHeight );
751 }
752 if ( posX < 0 ) {
753 posX = 0;
754 }
755 LOG.logDebug( "posX: " + posX );
756 LOG.logDebug( "posY: " + posY );
757
758 /**
759 * First translate then set the scale of the g2d and after the drawing reset the transformation.
760 */
761 g2d.translate( Math.round( posX * scaleX ), Math.round( posY * scaleY ) );
762 g2d.scale( scaleX, scaleY );
763 g2d.drawImage( stripeTexture, 0, 0, new Color( 0, 0, 0, 0 ), null );
764 g2d.setTransform( origTransform );
765 } else {
766 LOG.logError( "One of the resolutionStripes has no texture, so nothing to draw" );
767 }
768 }
769
770 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
771 LOG.logDebug( "The actual drawing on the g2d with preferred took: "
772 + ( ( System.currentTimeMillis() - paintTime ) / 1000d ) + "seconds." );
773 try {
774 File f = File.createTempFile( "resultTexture", ".png" );
775 f.deleteOnExit();
776 ImageIO.write( texture, "png", f );
777 LOG.logDebug( "Wrote texture: " + f.getAbsolutePath() );
778 } catch ( IOException e ) {
779 LOG.logDebug( "Could not write texture for debugging purposes because: ", e.getMessage() );
780 }
781 }
782 g2d.dispose();
783 }
784 /**
785 * Create a triangle terrain which will receive the texture.
786 */
787 TriangleTerrain terrain = new TriangleTerrain( terrainPoints, sceneBBox,
788 config.getDeegreeParams().getMinimalTerrainHeight(),
789 request.getScale() );
790 terrain.setTexture( texture );
791 terrain.createTerrain();
792 scene.addChild( terrain );
793 }
794 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
795 if ( dgmType == ResolutionStripe.ELEVATION_MODEL_POINTS ) {
796 LOG.logDebug( "The elevationmodel uses meassurepoints." );
797 } else if ( dgmType == ResolutionStripe.ELEVATION_MODEL_GRID ) {
798 LOG.logDebug( "The elevationmodel uses a grid." );
799 } else if ( dgmType == ResolutionStripe.ELEVATION_MODEL_UNKNOWN ) {
800 LOG.logDebug( "The elevationmodel uses an unknown format." );
801 }
802 }
803 for ( ResolutionStripe stripe : stripes ) {
804 // create one large triangled Shape3D object.
805 LOG.logDebug( "Getting Shape3D object from Stripe: " + stripe );
806 scene.addChild( stripe.getJava3DRepresentation() );
807 }
808
809 Calendar date = TimeTools.createCalendar( request.getVendorSpecificParameters().get( "DATETIME" ) );
810 return new WPVSScene( scene, viewPoint, date, null, createBackground( viewPoint, request ) );
811 }
812
813 /**
814 * Calculates the scale to fit the largest of the two given params to the maximum texture size.
815 *
816 * @param width
817 * the originalwidth of the texture
818 * @param height
819 * the original height of the texture
820 * @return a scale to fit the width and height in the configured maximumtexture width. or 1 if both are smaller.
821 */
822 private double getTextureScale( double width, double height ) {
823 if ( width > WPVSConfiguration.texture2DMaxSize || height > WPVSConfiguration.texture2DMaxSize ) {
824 return WPVSConfiguration.texture2DMaxSize / ( ( width > height ) ? width : height );
825 }
826 return 1;
827 }
828
829 /**
830 * Renders the scene and the resulting image.
831 *
832 * @return a new image representing a screen shot of the scene
833 */
834 public BufferedImage renderScene() {
835
836 BufferedImage image = renderer.renderScene();
837
838 if ( !config.getDeegreeParams().isWatermarked() ) {
839 paintCopyright( image );
840 }
841
842 return image;
843
844 }
845
846 /**
847 * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
848 * capabilities/configuration
849 *
850 * @param image
851 * the image onto which to print the copyright message/image
852 */
853 private void paintCopyright( BufferedImage image ) {
854
855 Graphics2D g2 = (Graphics2D) image.getGraphics();
856
857 WPVSDeegreeParams dp = config.getDeegreeParams();
858 String copyright = dp.getCopyright();
859 if ( config.getDeegreeParams().getCopyrightImage() != null ) {
860 g2.drawImage( config.getDeegreeParams().getCopyrightImage(), 0,
861 image.getHeight() - config.getDeegreeParams().getCopyrightImage().getHeight(), null );
862 } else if ( copyright != null && !"".equals( copyright ) ) {
863
864 g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
865 g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
866
867 final int fontSize = 14;
868 final int margin = 5;
869
870 int imgHeight = image.getHeight();
871
872 Font f = new Font( "SANSSERIF", Font.PLAIN, fontSize );
873 g2.setFont( f );
874 // draw text shadow
875 g2.setColor( Color.black );
876 g2.drawString( copyright, margin, imgHeight - margin );
877 // draw text
878 g2.setColor( Color.white );
879 g2.drawString( copyright, margin - 1, imgHeight - margin - 1 );
880
881 }
882 g2.dispose();
883 }
884
885 /**
886 * Checks if the image size is compatible with that given in the configuration
887 *
888 * @throws OGCWebServiceException
889 */
890 private void validateImageSize( GetView request )
891 throws OGCWebServiceException {
892 int width = request.getImageDimension().width;
893 int maxWidth = config.getDeegreeParams().getMaxViewWidth();
894 if ( width > maxWidth ) {
895 throw new OGCWebServiceException(
896 StringTools.concat(
897 100,
898 "Requested view width exceeds allowed maximum width of ",
899 new Integer( maxWidth ), " pixels." ) );
900 }
901 int height = request.getImageDimension().height;
902 int maxHeight = config.getDeegreeParams().getMaxViewHeight();
903 if ( height > maxHeight ) {
904 throw new OGCWebServiceException(
905 StringTools.concat(
906 100,
907 "Requested view height exceeds allowed maximum height of ",
908 new Integer( maxHeight ), " pixels." ) );
909 }
910
911 }
912
913 /**
914 * @return Returns the generated scene for this request... handy for debugging.
915 */
916 public WPVSScene getTheScene() {
917 return theScene;
918 }
919
920 /**
921 * @return the scene renderer.
922 */
923 public OffScreenWPVSRenderer getRenderer() {
924 return renderer;
925 }
926
927 }