001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wpvs/DefaultGetViewHandler.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2007 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/exse/
008 lat/lon GmbH
009 http://www.lat-lon.de
010
011 This library is free software; you can redistribute it and/or
012 modify it under the terms of the GNU Lesser General Public
013 License as published by the Free Software Foundation; either
014 version 2.1 of the License, or (at your option) any later version.
015
016 This library is distributed in the hope that it will be useful,
017 but WITHOUT ANY WARRANTY; without even the implied warranty of
018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 Lesser General Public License for more details.
020
021 You should have received a copy of the GNU Lesser General Public
022 License along with this library; if not, write to the Free Software
023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
024
025 Contact:
026
027 Andreas Poth
028 lat/lon GmbH
029 Aennchenstraße 19
030 53177 Bonn
031 Germany
032 E-Mail: poth@lat-lon.de
033
034 Prof. Dr. Klaus Greve
035 Department of Geography
036 University of Bonn
037 Meckenheimer Allee 166
038 53115 Bonn
039 Germany
040 E-Mail: greve@giub.uni-bonn.de
041
042 ---------------------------------------------------------------------------*/
043
044 package org.deegree.ogcwebservices.wpvs;
045
046 import java.awt.Color;
047 import java.awt.Font;
048 import java.awt.Graphics;
049 import java.awt.Graphics2D;
050 import java.awt.RenderingHints;
051 import java.awt.image.BufferedImage;
052 import java.io.IOException;
053 import java.net.URL;
054 import java.util.ArrayList;
055 import java.util.Calendar;
056 import java.util.HashMap;
057 import java.util.List;
058 import java.util.Set;
059 import java.util.SortedMap;
060 import java.util.TreeMap;
061 import java.util.concurrent.Callable;
062 import java.util.concurrent.CancellationException;
063
064 import javax.imageio.ImageIO;
065 import javax.media.j3d.Background;
066 import javax.media.j3d.BoundingSphere;
067 import javax.media.j3d.BranchGroup;
068 import javax.media.j3d.ImageComponent2D;
069 import javax.media.j3d.Node;
070 import javax.vecmath.Color3f;
071 import javax.vecmath.Point3d;
072
073 import org.deegree.framework.concurrent.ExecutionFinishedEvent;
074 import org.deegree.framework.concurrent.Executor;
075 import org.deegree.framework.log.ILogger;
076 import org.deegree.framework.log.LoggerFactory;
077 import org.deegree.framework.util.StringTools;
078 import org.deegree.framework.util.TimeTools;
079 import org.deegree.i18n.Messages;
080 import org.deegree.model.crs.CRSException;
081 import org.deegree.model.crs.CRSFactory;
082 import org.deegree.model.crs.CRSTransformationException;
083 import org.deegree.model.crs.CoordinateSystem;
084 import org.deegree.model.crs.GeoTransformer;
085 import org.deegree.model.crs.IGeoTransformer;
086 import org.deegree.model.spatialschema.Envelope;
087 import org.deegree.model.spatialschema.Position;
088 import org.deegree.model.spatialschema.Surface;
089 import org.deegree.ogcwebservices.InconsistentRequestException;
090 import org.deegree.ogcwebservices.OGCWebServiceException;
091 import org.deegree.ogcwebservices.wpvs.capabilities.Dataset;
092 import org.deegree.ogcwebservices.wpvs.capabilities.ElevationModel;
093 import org.deegree.ogcwebservices.wpvs.configuration.AbstractDataSource;
094 import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration;
095 import org.deegree.ogcwebservices.wpvs.configuration.WPVSDeegreeParams;
096 import org.deegree.ogcwebservices.wpvs.j3d.OffScreenWPVSRenderer;
097 import org.deegree.ogcwebservices.wpvs.j3d.TriangleTerrain;
098 import org.deegree.ogcwebservices.wpvs.j3d.ViewPoint;
099 import org.deegree.ogcwebservices.wpvs.j3d.WPVSScene;
100 import org.deegree.ogcwebservices.wpvs.operation.GetView;
101 import org.deegree.ogcwebservices.wpvs.operation.GetViewResponse;
102 import org.deegree.ogcwebservices.wpvs.utils.QuadTreeSplitter;
103 import org.deegree.ogcwebservices.wpvs.utils.ResolutionStripe;
104 import org.deegree.ogcwebservices.wpvs.utils.StripeFactory;
105
106 import com.sun.j3d.utils.image.TextureLoader;
107
108 /**
109 * Default handler for WPVS GetView requests. This Class is the central position where the {@link GetView} request lands
110 * the configured Datasources are gathered and the scene is put together.
111 *
112 * @author <a href="mailto:taddei@lat-lon.de">Ugo Taddei</a>
113 * @author last edited by: $Author: rbezema $
114 *
115 * $Revision: 7965 $, $Date: 2007-08-09 12:03:55 +0200 (Do, 09 Aug 2007) $
116 *
117 */
118 public class DefaultGetViewHandler extends GetViewHandler {
119
120 static private final ILogger LOG = LoggerFactory.getLogger( DefaultGetViewHandler.class );
121
122 private WPVSConfiguration config;
123
124 private URL backgroundImgURL;
125
126 private WPVSScene theScene;
127
128 private OffScreenWPVSRenderer renderer;
129
130 /**
131 * Constructor for DefaultGetViewHandler.
132 *
133 * @param owner
134 * the service creating this handler
135 */
136 public DefaultGetViewHandler( WPVService owner ) {
137 super( owner );
138
139 this.config = owner.getConfiguration();
140 }
141
142 /**
143 * This Method handles a clients GetView request by creating the appropriate (configured) Datasources, the
144 * {@link ResolutionStripe}s, the requeststripes (which are actually axisalligned Resolutionsripes) and finally
145 * putting them all together in a java3d scene. The creation of the Shape3D Objects (by requesting the
146 * ResolutionStripe relevant Datasources) is done in separate Threads for each ResolutionStripe (which is in
147 * conflict with the deegree styleguides) using the {@link Executor} class.
148 *
149 * @see org.deegree.ogcwebservices.wpvs.GetViewHandler#handleRequest(org.deegree.ogcwebservices.wpvs.operation.GetView)
150 */
151 @Override
152 public GetViewResponse handleRequest( final GetView request )
153 throws OGCWebServiceException {
154
155 // request = req;
156
157 validateImageSize( request );
158
159 List<Dataset> validDatasets = new ArrayList<Dataset>();
160 String errorMessage = getValidRequestDatasets( request, validDatasets );
161 if ( validDatasets.size() == 0 ) {
162 throw new OGCWebServiceException(
163 StringTools.concat(
164 errorMessage.length() + 100,
165 "Your request yields no results for given reasons:\n",
166 errorMessage ) );
167
168 }
169
170 ElevationModel elevationModel = getValidElevationModel( request.getElevationModel() );
171 LOG.logDebug( "Requested elevationModel: " + elevationModel );
172
173 if ( request.getFarClippingPlane() > config.getDeegreeParams().getMaximumFarClippingPlane() ) {
174 request.setFarClippingPlane( config.getDeegreeParams().getMaximumFarClippingPlane() );
175 }
176
177 ViewPoint viewPoint = new ViewPoint( request, getTerrainHeightAboveSeaLevel( null ) );
178 LOG.logDebug( "Viewpoint: " + viewPoint );
179
180 // viewPoint.setTerrainDistanceToSeaLevel( getTerrainHeightAboveSeaLevel(
181 // viewPoint.getObserverPosition() ) );
182
183 ArrayList<ResolutionStripe> resolutionStripes = createRequestBoxes( request, viewPoint,
184 config.getSmallestMinimalScaleDenomiator() );
185 LOG.logDebug( "Found number of resolutionStripes: " + resolutionStripes.size() );
186
187 if ( resolutionStripes.size() == 0 ) {
188 throw new OGCWebServiceException(
189 StringTools.concat( 200,
190 "There were no RequestBoxes found, therefor this WPVS-request is invalid" ) );
191
192 }
193
194 Surface visibleArea = viewPoint.getVisibleArea();
195
196 LOG.logDebug( "Visible Area: " + visibleArea );
197
198 findValidDataSourcesFromDatasets( validDatasets, resolutionStripes, request.getOutputFormat(), visibleArea );
199 LOG.logDebug( "Using validDatasets: " + validDatasets );
200
201 findValidEMDataSourceFromElevationModel( elevationModel, resolutionStripes, visibleArea );
202 LOG.logDebug( "Using elevationModel: " + elevationModel );
203
204 // will also check is background is valid, before doing any hard work
205 this.backgroundImgURL = createBackgroundImageURL( request );
206
207
208 LOG.logDebug( "Backgroundurl : " + backgroundImgURL );
209
210 Executor exec = Executor.getInstance();
211 List<ExecutionFinishedEvent<ResolutionStripe>> resultingBoxes = null;
212 try {
213 resultingBoxes = exec.performSynchronously( new ArrayList<Callable<ResolutionStripe>>( resolutionStripes ),
214 config.getDeegreeParams().getRequestTimeLimit() );
215 } catch ( InterruptedException ie ) {
216 throw new OGCWebServiceException(
217 StringTools.concat( 200,
218 "A Threading-Error occurred while placing your request." ) );
219
220 }
221
222 LOG.logDebug( "All resolutionstripes finished executing." );
223
224 if ( resultingBoxes != null && resultingBoxes.size() > 0 ) {
225 // check if some of the resolutionstripes finished execution before the configured max
226 // life time.
227 int k = 0;
228 for ( ExecutionFinishedEvent<ResolutionStripe> efe : resultingBoxes ) {
229 try {
230 efe.getResult();
231 } catch ( CancellationException ce ) {
232 k++;
233 } catch ( Throwable e ) {
234 // not interested in something else
235 }
236 }
237 if ( k == resultingBoxes.size() ) {
238 throw new OGCWebServiceException(
239 Messages.getMessage(
240 "WPVS_EXCEEDED_REQUEST_TIME",
241 new Double(
242 config.getDeegreeParams().getRequestTimeLimit() * 0.001 ) ).toString() );
243 }
244 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
245 LOG.logDebug( "Outputting textures for all finished resolutionsSripes." );
246 for ( ResolutionStripe stripe : resolutionStripes ) {
247 stripe.outputTextures();
248 }
249 }
250 theScene = createScene( request, viewPoint, resolutionStripes );
251
252 renderer = new OffScreenWPVSRenderer( config.getDeegreeParams().getNearClippingPlane(), theScene, request.getImageDimension().width,
253 request.getImageDimension().height );
254 LOG.logDebug( "Trying to render Scene." );
255 BufferedImage output = renderScene();
256 LOG.logDebug( "Returning rendered Scene." );
257 return new GetViewResponse( output, request.getOutputFormat() );
258 }
259 return null;
260 }
261
262 /**
263 * @param elevationModelName
264 * @return an elevationModell or <code>null</code> if no name was given
265 * @throws OGCWebServiceException
266 * if no elevationModel is found for the given elevationmodelname
267 */
268 private ElevationModel getValidElevationModel( String elevationModelName )
269 throws OGCWebServiceException {
270 ElevationModel resultEMModel = null;
271 if ( elevationModelName != null ) {
272 resultEMModel = config.findElevationModel( elevationModelName );// dataset.getElevationModel();
273 if ( resultEMModel == null ) {
274 throw new OGCWebServiceException( StringTools.concat( 150, "ElevationModel '", elevationModelName,
275 "' is not known to the WPVS" ) );
276 }
277 }
278 return resultEMModel;
279 }
280
281 /**
282 * Finds the datasets which can handle the requested crs's, and check if they are defined inside the requested bbox
283 *
284 * @param request
285 * the GetView request
286 * @param coordSys
287 * the name of the requested coordsys
288 * @return an ArrayList containing all the datasets which comply with the requested crs.
289 * @throws OGCWebServiceException
290 */
291 private String getValidRequestDatasets( GetView request, List<Dataset> resultDatasets )
292 throws OGCWebServiceException {
293 // ArrayList<Dataset> resultDatasets = new ArrayList<Dataset>();
294 Envelope bbox = request.getBoundingBox();
295 List<String> datasets = request.getDatasets();
296 CoordinateSystem coordSys = request.getCrs();
297
298 // If the BoundingBox request not is
299 try {
300 if ( !"EPSG:4326".equalsIgnoreCase( coordSys.getFormattedString() ) ) {
301 // transform the bounding box of the request to EPSG:4326/WGS 84
302 IGeoTransformer gt = new GeoTransformer( CRSFactory.create( "EPSG:4326" ) );
303 bbox = gt.transform( bbox, coordSys );
304 }
305 } catch ( CRSTransformationException e ) {
306 LOG.logError( e.getMessage(), e );
307 throw new OGCWebServiceException( e.getMessage() );
308 } catch ( CRSException e ) {
309 LOG.logError( e.getMessage(), e );
310 throw new OGCWebServiceException( e.getMessage() );
311 }
312 StringBuffer errorMessage = new StringBuffer( 1000 );
313 for ( String dset : datasets ) {
314 Dataset configuredDataset = config.findDataset( dset );
315 if ( configuredDataset != null ) {
316 CoordinateSystem[] dataSetCRS = configuredDataset.getCrs();
317 boolean isCoordSysSupported = false;
318 boolean requestIntersectsWithDataset = false;
319 for ( CoordinateSystem crs : dataSetCRS ) {
320 if ( crs.equals( coordSys ) ) {
321 isCoordSysSupported = true;
322 // lookslike compatible crs therefor check if bbox intersect
323 if ( configuredDataset.getWgs84BoundingBox().intersects( bbox ) ) {
324 requestIntersectsWithDataset = true;
325 if ( !resultDatasets.contains( configuredDataset ) )
326 resultDatasets.add( configuredDataset );
327 }
328 }
329 }
330 if ( !isCoordSysSupported ) {
331 errorMessage.append( "Requested Dataset -" + dset + "- does not support requested crs: " + coordSys
332 + ".\n" );
333 } else if ( !requestIntersectsWithDataset ) {
334 errorMessage.append( "Requested Dataset -" + dset
335 + "- does not intersect with the requested bbox.\n" );
336 }
337 } else {
338 throw new InconsistentRequestException( Messages.getMessage( "WPVS_GETVIEW_INVALID_DATASET", dset ) );
339 }
340 }
341
342 return errorMessage.toString();
343 }
344
345 /**
346 * Finds the valid datasources in the configured (and allready checked) datasets.
347 *
348 * @param datasets
349 * the datasets which are valid for this request
350 */
351 private void findValidDataSourcesFromDatasets( List<Dataset> datasets, ArrayList<ResolutionStripe> stripes,
352 String outputFormat, Surface visibleArea ) {
353 double resolution = 0;
354 for ( ResolutionStripe stripe : stripes ) {
355 stripe.setOutputFormat( outputFormat );
356 resolution = stripe.getMaxResolutionAsScaleDenominator();
357 for ( int i = 0; i < datasets.size(); ++i ) {
358 Dataset dset = datasets.get( i );
359 AbstractDataSource[] dataSources = dset.getDataSources();
360
361 for ( AbstractDataSource ads : dataSources ) {
362 // System.out.println( "AbstractDataSource: " + ads );
363 if ( resolution >= dset.getMinimumScaleDenominator()
364 && resolution < dset.getMaximumScaleDenominator()
365 && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
366 && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
367 if ( ads.getServiceType() == AbstractDataSource.LOCAL_WFS
368 || ads.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
369 stripe.addFeatureCollectionDataSource( ads );
370 } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS
371 || ads.getServiceType() == AbstractDataSource.REMOTE_WMS
372 || ads.getServiceType() == AbstractDataSource.LOCAL_WCS
373 || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
374 stripe.addTextureDataSource( ads );
375 }
376 }
377 }
378 }
379 }
380 }
381
382 /**
383 * @param elevationModel
384 * and it's datasources.
385 */
386 private void findValidEMDataSourceFromElevationModel( ElevationModel elevationModel,
387 ArrayList<ResolutionStripe> stripes, Surface visibleArea ) {
388 if ( elevationModel != null ) {
389 AbstractDataSource[] emDataSources = elevationModel.getDataSources();
390 Dataset dataset = elevationModel.getParentDataset();
391 for ( ResolutionStripe stripe : stripes ) {
392 double resolution = stripe.getMaxResolution();
393 for ( AbstractDataSource ads : emDataSources ) {
394 if ( resolution >= dataset.getMinimumScaleDenominator()
395 && resolution < dataset.getMaximumScaleDenominator()
396 && resolution >= ads.getMinScaleDenominator() && resolution < ads.getMaxScaleDenominator()
397 && ( ( ads.getValidArea() != null ) ? ads.getValidArea().intersects( visibleArea ) : true ) ) {
398 stripe.setElevationModelDataSource( ads );
399 }
400 }
401 }
402 }
403 }
404
405 /**
406 * Extracts from the request and the configuration the URL behind teh name of a given BACKGROUND
407 *
408 * @return the URL, under which the background image is found
409 * @throws OGCWebServiceException
410 * if no URL with the name given by 'BACKGROUND' can be found.
411 */
412 private URL createBackgroundImageURL( GetView request )
413 throws OGCWebServiceException {
414
415 String imageName = request.getVendorSpecificParameter( "BACKGROUND" );
416 URL imgURL = null;
417 if ( imageName != null ) {
418 imgURL = (URL) config.getDeegreeParams().getBackgroundMap().get( imageName );
419 if ( imgURL == null ) {
420 String s = StringTools.concat( 100, "Cannot find any image referenced", "by parameter BACKGROUND=",
421 imageName );
422 throw new OGCWebServiceException( s );
423 }
424 }
425
426 return imgURL;
427 }
428
429 /**
430 * Creates a Java3D Node representing the background.
431 *
432 * @param minAltitude
433 * the minimum altitude of the backgroudn plane
434 * @param viewPoint
435 * the viewersposition
436 * @return a new Node containing a geometry representing the background
437 * @throws IOException
438 */
439 private Node createBackground( ViewPoint viewPoint, GetView request )
440 throws OGCWebServiceException {
441
442 Point3d observer = viewPoint.getObserverPosition();
443
444 Background bg = new Background( new Color3f( request.getBackgroundColor() ) );
445 BoundingSphere bounds = new BoundingSphere( observer,
446 config.getDeegreeParams().getMaximumFarClippingPlane() * 1.5 );
447
448 bg.setApplicationBounds( bounds );
449 try {
450 if ( backgroundImgURL != null ) {
451 BufferedImage buffImg = ImageIO.read( backgroundImgURL );
452
453 // scale image to fill the whole bakground
454 BufferedImage tmpImg = new BufferedImage( request.getImageDimension().width,
455 request.getImageDimension().height, buffImg.getType() );
456 Graphics g = tmpImg.getGraphics();
457 g.drawImage( buffImg, 0, 0, tmpImg.getWidth() - 1, tmpImg.getHeight() - 1, null );
458 g.dispose();
459
460 ImageComponent2D img = new TextureLoader( tmpImg ).getImage();
461 bg.setImage( img );
462 }
463 } catch ( IOException e ) {
464 LOG.logError( e.getMessage(), e );
465 String s = StringTools.concat( 100, "Could not create backgound image: ", e.getMessage() );
466 throw new OGCWebServiceException( s );
467 }
468
469 return bg;
470 }
471
472 /**
473 * Creates the request boxes from the parameters available i teh incoming request.
474 *
475 * @param viewPoint
476 * where the viewer is
477 * @return a new array of surfaces representing the area in which data will be collected
478 */
479 private ArrayList<ResolutionStripe> createRequestBoxes( GetView request, ViewPoint viewPoint,
480 double smallestMinimalScaleDenominator ) {
481 ArrayList<ResolutionStripe> requestStripes = new ArrayList<ResolutionStripe>();
482 String splittingMode = request.getVendorSpecificParameter( "SPLITTER" );
483 StripeFactory stripesFactory = new StripeFactory( viewPoint, smallestMinimalScaleDenominator );
484 int imageWidth = request.getImageDimension().width;
485 if ( "BBOX".equals( splittingMode ) ) {
486 requestStripes = stripesFactory.createBBoxResolutionStripe(
487 request.getBoundingBox(),
488 imageWidth,
489 getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
490 request.getScale() );
491 } else {
492 // Calculate the resolution stripes perpendicular to the viewdirection
493 requestStripes = stripesFactory.createResolutionStripes(
494 request.getImageDimension().width,
495 getTerrainHeightAboveSeaLevel( viewPoint.getPointOfInterest() ),
496 null, request.getScale() );
497 QuadTreeSplitter splittree = new QuadTreeSplitter( requestStripes, request.getImageDimension().width,
498 config.getDeegreeParams().isRequestQualityPreferred(),
499 null );
500 requestStripes = splittree.getRequestQuads( null, config.getDeegreeParams().getExtendRequestPercentage() );
501 }
502 return requestStripes;
503 }
504
505 /**
506 * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
507 * configured MinimalTerrainHeight.
508 *
509 * @param eyePositionX
510 * to find the height of the sealevel for (not used yet)
511 * @param eyePositionY
512 * to find the height of the sealevel for (not used yet)
513 * @param eyePositionZ
514 * to find the height of the sealevel for (not used yet)
515 * @return the height above the seaLevel.
516 */
517 protected double getTerrainHeightAboveSeaLevel( double eyePositionX, double eyePositionY, double eyePositionZ ) {
518 return getTerrainHeightAboveSeaLevel( new Point3d( eyePositionX, eyePositionY, eyePositionZ ) );
519 }
520
521 /**
522 * Should return the height of the terrain above the sealevel. TODO just returns a constant value, i.e. the
523 * configured MinimalTerrainHeight.
524 *
525 * @param eyePosition
526 * to find the height of the sealevel for (not used yet)
527 *
528 * @return the height above the seaLevel.
529 */
530 protected double getTerrainHeightAboveSeaLevel( @SuppressWarnings("unused")
531 Point3d eyePosition ) {
532 return config.getDeegreeParams().getMinimalTerrainHeight();
533 }
534
535 /**
536 * Creates a WPVS scene
537 *
538 * @param viewPoint
539 * position of the viewer
540 * @return a new scene
541 * @throws OGCWebServiceException
542 * if the background img cannot be read
543 */
544 private WPVSScene createScene( GetView request, ViewPoint viewPoint, List<ResolutionStripe> stripes )
545 throws OGCWebServiceException {
546
547 if ( stripes == null && stripes.size() == 0 ) {
548 return null;
549 }
550 LOG.logDebug( "Creating scene with " + stripes.size() + " number of resolution stripes " );
551 BranchGroup scene = new BranchGroup();
552
553 if ( stripes.get( 0 ).isDGMFromMeassurePoints() ) {
554 LOG.logDebug( "The elevation model uses points, therefore trying to 'sow' the patches to eachother." );
555 // first create an overall texture which will contain all textures
556 // double minX = Double.MAX_VALUE;
557 // double minY = Double.MAX_VALUE;
558 // double maxX = Double.MIN_VALUE;
559 // double maxY = Double.MIN_VALUE;
560
561 // sort the resolutionstripe according to their y and x values of the minimal Point of
562 // their boundingboxes.
563 TreeMap<Double, SortedMap<Double, TriangleTerrain>> sortedStripesMinY = new TreeMap<Double, SortedMap<Double, TriangleTerrain>>();
564 TreeMap<Double, SortedMap<Double, TriangleTerrain>> sortedStripesMinX = new TreeMap<Double, SortedMap<Double, TriangleTerrain>>();
565
566 // List<Point> measurePoints = new ArrayList<Point>();
567 for ( ResolutionStripe stripe : stripes ) {
568 Position tmpMin = stripe.getSurface().getEnvelope().getMin();
569 Double minXValue = new Double( tmpMin.getX() );
570 Double minYValue = new Double( tmpMin.getY() );
571 // if ( textureWidth < stripe.getRequestWidthForBBox() ) {
572 // textureWidth = stripe.getRequestWidthForBBox();
573 // }
574 // measurePoints.addAll( stripe.getMeasurePoints() );
575
576 // Position tmpMax = stripe.getSurface().getEnvelope().getMax();
577 // Double maxXValue = new Double( tmpMax.getX() );
578 // Double maxYValue = new Double( tmpMax.getY() );
579 //
580 // // defining epsilon area around the key
581 Double fromMinYSmall = new Double( tmpMin.getY() - 0.001 );
582 Double fromMinYLarge = new Double( tmpMin.getY() + 0.001 );
583 Double fromMinXSmall = new Double( tmpMin.getX() - 0.001 );
584 Double fromMinXLarge = new Double( tmpMin.getX() + 0.001 );
585
586 // finding keys which are epsilon near to the min and max values
587 SortedMap<Double, SortedMap<Double, TriangleTerrain>> tileMinYMap = sortedStripesMinY.subMap(
588 fromMinYSmall,
589 fromMinYLarge );
590 SortedMap<Double, SortedMap<Double, TriangleTerrain>> tileMinXMap = sortedStripesMinX.subMap(
591 fromMinXSmall,
592 fromMinXLarge );
593 // sort to y and then to x
594
595 if ( !tileMinYMap.isEmpty() ) {
596 SortedMap<Double, TriangleTerrain> stripeTree = tileMinYMap.get( tileMinYMap.firstKey() );
597 stripeTree.put( minXValue, (TriangleTerrain) stripe.getElevationModel() );
598 } else {// no matching min_Y_key found, create new Tree for this min_X_Value
599 SortedMap<Double, TriangleTerrain> stripeTree = new TreeMap<Double, TriangleTerrain>();
600 stripeTree.put( minXValue, (TriangleTerrain) stripe.getElevationModel() );
601 sortedStripesMinY.put( minYValue, stripeTree );
602 }
603 // sort to x and than to y
604 if ( !tileMinXMap.isEmpty() ) {
605 SortedMap<Double, TriangleTerrain> stripeTree = tileMinXMap.get( tileMinXMap.firstKey() );
606 stripeTree.put( minYValue, (TriangleTerrain) stripe.getElevationModel() );
607 } else {// no matching min_X_key found, create new Tree for this min_X_Value
608 SortedMap<Double, TriangleTerrain> stripeTree = new TreeMap<Double, TriangleTerrain>();
609 stripeTree.put( minYValue, (TriangleTerrain) stripe.getElevationModel() );
610 sortedStripesMinX.put( minXValue, stripeTree );
611 }
612
613 // if ( tmpMin.getX() < minX ) {
614 // minX = tmpMin.getX();
615 // }
616 // if ( tmpMin.getY() < minY ) {
617 // minY = tmpMin.getY();
618 // }
619 // if ( tmpMax.getX() > maxX ) {
620 // maxX = tmpMax.getX();
621 // }
622 // if ( tmpMax.getY() > maxY ) {
623 // maxY = tmpMax.getY();
624 // }
625 }
626
627 createSeamsWithScanline( sortedStripesMinY, true );
628 createSeamsWithScanline( sortedStripesMinX, false );
629 }
630
631 // dgm is a heightmap
632 for ( ResolutionStripe stripe : stripes ) {
633 // create one large triangled Shape3D object.
634 LOG.logDebug( "Getting Shape3D object for Stripe: " + stripe );
635 scene.addChild( stripe.getJava3DRepresentation() );
636 }
637 // TriangleTerrain tt = new TriangleTerrain();
638 Calendar date = TimeTools.createCalendar( request.getVendorSpecificParameters().get( "DATETIME" ) );
639 return new WPVSScene( scene, viewPoint, date, null, createBackground( viewPoint, request ) );
640
641 }
642
643 private void createSeamsWithScanline( SortedMap<Double, SortedMap<Double, TriangleTerrain>> sortedTerrains,
644 boolean ySorted ) {
645 Set<Double> firstCoordinates = sortedTerrains.keySet();
646 SortedMap<Double, TriangleTerrain> currentScanline = new TreeMap<Double, TriangleTerrain>();
647 // ArrayList<Double> removeFromScanline = new ArrayList<Double>( 10 );
648
649 HashMap<TriangleTerrain, ArrayList<TriangleTerrain>> visitedPairs = new HashMap<TriangleTerrain, ArrayList<TriangleTerrain>>();
650
651 for ( Double firstCoordinate : firstCoordinates ) {
652 // System.out.println( "\n*******\nnew firstkey\n*********\n" );
653 SortedMap<Double, TriangleTerrain> resMap = sortedTerrains.get( firstCoordinate );
654 if ( resMap != null ) {
655 Set<Double> secondCoordinates = resMap.keySet();
656 for ( Double secondCoordinate : secondCoordinates ) {
657 TriangleTerrain terrain = resMap.get( secondCoordinate );
658 if ( terrain != null ) {
659 // System.out.println( "found a nother stripe" );
660 currentScanline.put( secondCoordinate, terrain );
661 }
662 }
663 }
664 Set<Double> keySet = currentScanline.keySet();
665 if ( keySet != null && keySet.size() > 0 ) {
666 Double[] scanlineKeys = new Double[keySet.size()];
667 keySet.toArray( scanlineKeys );
668 // remove all passed stripes from the current scanline set
669 for ( int i = 0; i < scanlineKeys.length; ++i ) {
670 TriangleTerrain terrain = currentScanline.get( scanlineKeys[i] );
671 double comparator = ( ySorted ) ? terrain.getBoundingBox().getMax().getY()
672 : terrain.getBoundingBox().getMax().getX();
673 if ( firstCoordinate.doubleValue() >= comparator ) {
674 // remove from visited too
675 ArrayList<TriangleTerrain> allNeighbours = visitedPairs.get( terrain );
676 if ( allNeighbours != null ) {
677 allNeighbours.clear();
678 visitedPairs.remove( terrain );
679 }
680 currentScanline.remove( scanlineKeys[i] );
681 }
682 }
683 // iterate over all resolutionstripes currently "touched" by the scanline.
684 if ( keySet.size() > 0 ) {
685 scanlineKeys = new Double[keySet.size()];
686 keySet.toArray( scanlineKeys );
687 for ( int i = 0; i < scanlineKeys.length; ++i ) {
688 TriangleTerrain terrain = currentScanline.get( scanlineKeys[i] );
689
690 if ( ( i + 1 ) < scanlineKeys.length ) {
691 TriangleTerrain neighbourTerrain = currentScanline.get( scanlineKeys[i + 1] );
692 ArrayList<TriangleTerrain> visitedNeighbours = visitedPairs.get( terrain );
693 boolean visited = false;
694 if ( visitedNeighbours != null ) {
695 visited = visitedNeighbours.contains( neighbourTerrain );
696 }
697 // two TriangleTerrains in the scanline have never been neighbours,
698 // therefore a seam must be created according to the sorting order
699 if ( !visited ) {
700 if ( ySorted ) {
701 terrain.createVerticalSeam( neighbourTerrain );
702 } else {
703 terrain.createHorizontalSeam( neighbourTerrain );
704 }
705 // and mark as neighbours.
706 if ( visitedNeighbours != null ) {
707 visitedNeighbours.add( neighbourTerrain );
708 } else {
709 visitedNeighbours = new ArrayList<TriangleTerrain>( 5 );
710 visitedNeighbours.add( neighbourTerrain );
711 visitedPairs.put( terrain, visitedNeighbours );
712 }
713 }
714 }
715 }
716 }
717 }
718 }
719 }
720
721 /**
722 * Renders the scene and the resulting image.
723 *
724 * @return a new image representing a screen shot of the scene
725 */
726 public BufferedImage renderScene() {
727
728 BufferedImage image = renderer.renderScene();
729
730 if ( !config.getDeegreeParams().isWatermarked() ) {
731 paintCopyright( image );
732 }
733
734 return image;
735
736 }
737
738 /**
739 * prints a copyright note at left side of the map bottom. The copyright note will be extracted from the WMS
740 * capabilities/configuration
741 *
742 * @param image
743 * the image onto which to print the copyright message/image
744 */
745 private void paintCopyright( BufferedImage image ) {
746
747 Graphics2D g2 = (Graphics2D) image.getGraphics();
748
749 WPVSDeegreeParams dp = config.getDeegreeParams();
750 String copyright = dp.getCopyright();
751 if ( config.getDeegreeParams().getCopyrightImage() != null ) {
752 g2.drawImage( config.getDeegreeParams().getCopyrightImage(), 0,
753 image.getHeight() - config.getDeegreeParams().getCopyrightImage().getHeight(), null );
754 } else if ( copyright != null ) {
755
756 g2.setRenderingHint( RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON );
757 g2.setRenderingHint( RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON );
758
759 final int fontSize = 14;
760 final int margin = 5;
761
762 int imgHeight = image.getHeight();
763
764 Font f = new Font( "SANSSERIF", Font.PLAIN, fontSize );
765 g2.setFont( f );
766 // draw text shadow
767 g2.setColor( Color.black );
768 g2.drawString( copyright, margin, imgHeight - margin );
769 // draw text
770 g2.setColor( Color.white );
771 g2.drawString( copyright, margin - 1, imgHeight - margin - 1 );
772
773 }
774 g2.dispose();
775 }
776
777 /**
778 * Checks if the image size is compatible with that given in the configuration
779 *
780 * @throws OGCWebServiceException
781 */
782 private void validateImageSize( GetView request )
783 throws OGCWebServiceException {
784 int width = request.getImageDimension().width;
785 int maxWidth = config.getDeegreeParams().getMaxViewWidth();
786 if ( width > maxWidth ) {
787 throw new OGCWebServiceException(
788 StringTools.concat(
789 100,
790 "Requested view width exceeds allowed maximum width of ",
791 new Integer( maxWidth ), " pixels." ) );
792 }
793 int height = request.getImageDimension().height;
794 int maxHeight = config.getDeegreeParams().getMaxViewHeight();
795 if ( height > maxHeight ) {
796 throw new OGCWebServiceException(
797 StringTools.concat(
798 100,
799 "Requested view height exceeds allowed maximum height of ",
800 new Integer( maxHeight ), " pixels." ) );
801 }
802
803 }
804
805 /**
806 * @return Returns the generated scene for this request... handy for debugging.
807 */
808 public WPVSScene getTheScene() {
809 return theScene;
810 }
811
812 /**
813 * @return the scene renderer.
814 */
815 public OffScreenWPVSRenderer getRenderer() {
816 return renderer;
817 }
818
819 }