001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/ogcwebservices/wpvs/utils/ResolutionStripe.java $
002 /*---------------- FILE HEADER ------------------------------------------
003
004 This file is part of deegree.
005 Copyright (C) 2001-2008 by:
006 EXSE, Department of Geography, University of Bonn
007 http://www.giub.uni-bonn.de/deegree/
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 Aennchenstr. 19
030 53115 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
045 package org.deegree.ogcwebservices.wpvs.utils;
046
047 import java.awt.BasicStroke;
048 import java.awt.Color;
049 import java.awt.Font;
050 import java.awt.Graphics2D;
051 import java.awt.Stroke;
052 import java.awt.font.TextLayout;
053 import java.awt.geom.Rectangle2D;
054 import java.awt.image.BufferedImage;
055 import java.io.File;
056 import java.io.IOException;
057 import java.util.ArrayList;
058 import java.util.Collection;
059 import java.util.HashMap;
060 import java.util.Iterator;
061 import java.util.List;
062 import java.util.Random;
063 import java.util.Set;
064 import java.util.concurrent.Callable;
065
066 import javax.imageio.ImageIO;
067 import javax.media.j3d.OrderedGroup;
068 import javax.vecmath.Point3d;
069 import javax.vecmath.Vector3f;
070
071 import org.deegree.framework.log.ILogger;
072 import org.deegree.framework.log.LoggerFactory;
073 import org.deegree.framework.util.MapUtils;
074 import org.deegree.framework.util.StringTools;
075 import org.deegree.i18n.Messages;
076 import org.deegree.model.crs.CoordinateSystem;
077 import org.deegree.model.spatialschema.Envelope;
078 import org.deegree.model.spatialschema.GeometryException;
079 import org.deegree.model.spatialschema.Position;
080 import org.deegree.model.spatialschema.Surface;
081 import org.deegree.model.spatialschema.WKTAdapter;
082 import org.deegree.ogcwebservices.OGCWebServiceException;
083 import org.deegree.ogcwebservices.wpvs.GetViewServiceInvoker;
084 import org.deegree.ogcwebservices.wpvs.WCSInvoker;
085 import org.deegree.ogcwebservices.wpvs.WFSInvoker;
086 import org.deegree.ogcwebservices.wpvs.WMSInvoker;
087 import org.deegree.ogcwebservices.wpvs.configuration.AbstractDataSource;
088 import org.deegree.ogcwebservices.wpvs.configuration.WPVSConfiguration;
089 import org.deegree.ogcwebservices.wpvs.j3d.DefaultSurface;
090 import org.deegree.ogcwebservices.wpvs.j3d.TerrainModel;
091 import org.deegree.ogcwebservices.wpvs.j3d.TexturedHeightMapTerrain;
092 import org.deegree.ogcwebservices.wpvs.j3d.TriangleTerrain;
093 import org.deegree.processing.raster.converter.Image2RawData;
094 import org.deegree.processing.raster.filter.Convolve;
095 import org.deegree.processing.raster.filter.RasterFilterException;
096 import org.j3d.geom.GeometryData;
097
098 /**
099 * The <code>ResolutionStripe</code> class encapsulates a Surface with a maximum Resolution, which is convenient for
100 * the creation of a quadtree.
101 *
102 * @author <a href="mailto:bezema@lat-lon.de">Rutger Bezema</a>
103 *
104 * @author last edited by: $Author: apoth $
105 *
106 * @version $Revision: 9345 $, $Date: 2007-12-27 17:22:25 +0100 (Do, 27 Dez 2007) $
107 *
108 */
109
110 public class ResolutionStripe implements Callable<ResolutionStripe>, Comparable {
111 private final static ILogger LOG = LoggerFactory.getLogger( ResolutionStripe.class );
112
113 /**
114 * No information about the elevationmodel is known (e.g. no elevationModel is set)
115 */
116 public final static int ELEVATION_MODEL_UNKNOWN = 0;
117
118 /**
119 * The elevationmodel uses a grid data serving datasource (e.g. CSW)
120 */
121 public final static int ELEVATION_MODEL_GRID = 1;
122
123 /**
124 * The elevationmodel uses a point data serving datasource (e.g WFS)
125 */
126 public final static int ELEVATION_MODEL_POINTS = 2;
127
128 private final double maxResolution;
129
130 private final double minResolution;
131
132 private Surface surface;
133
134 private TerrainModel elevationModel = null;
135
136 private AbstractDataSource elevationModelDataSource = null;
137
138 private HashMap<String, BufferedImage> textures;
139
140 private HashMap<String, OGCWebServiceException> textureExceptions;
141
142 private ArrayList<AbstractDataSource> texturesDataSources;
143
144 private HashMap<String, DefaultSurface> features;
145
146 private ArrayList<AbstractDataSource> featureCollectionDataSources;
147
148 private double minimalHeightlevel;
149
150 private String outputFormat = null;
151
152 private OrderedGroup resultingJ3DScene;
153
154 private double scale;
155
156 private int dgmType;
157
158 private BufferedImage resultTexture = null;
159
160 private List<Point3d> pointList;
161
162 // a string for debugging containing the old resolution, if the resolution was fixed because of the maxRequestSize.
163 private String debugString = null;
164
165 /**
166 * @param surface
167 * of this resolution stripe, after the stripeFactory it is a trapezium, after the quad-splitter an
168 * axis-alligned bbox, which will be a request.
169 * @param maximumResolution
170 * the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
171 * viewer.
172 * @param minimumResolution
173 * the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
174 * @param minimalHeight
175 * the terrain height which will be taken if an error occurred while retrieving height data.
176 * @param scale
177 * of the heights ( 1.0 means no scaling )
178 */
179 public ResolutionStripe( Surface surface, double maximumResolution, double minimumResolution, double minimalHeight,
180 double scale ) {
181 this.surface = surface;
182 // calculate the requestWidth and requestHeight.
183 int rH = (int) Math.round( surface.getEnvelope().getHeight() / Math.abs( minimumResolution ) );
184 int rW = (int) Math.round( surface.getEnvelope().getWidth() / Math.abs( minimumResolution ) );
185
186 if ( rH > WPVSConfiguration.MAX_REQUEST_SIZE || rH < 0 || rW > WPVSConfiguration.MAX_REQUEST_SIZE || rW < 0 ) {
187 minResolution = ( ( rH > rW ) ? surface.getEnvelope().getHeight() : surface.getEnvelope().getWidth() ) / WPVSConfiguration.MAX_REQUEST_SIZE;
188 maxResolution = minResolution;
189 debugString = "fixed resolution: " + minimumResolution;
190 LOG.logDebug( "Setting maxResolution to " + minResolution
191 + " instead of "
192 + minimumResolution
193 + " because the request width or height would be larger as + "
194 + WPVSConfiguration.MAX_REQUEST_SIZE
195 + " pixel." );
196 } else {
197 this.minResolution = minimumResolution;
198 this.maxResolution = maximumResolution;
199 }
200 this.minimalHeightlevel = minimalHeight;
201 this.scale = scale;
202 featureCollectionDataSources = new ArrayList<AbstractDataSource>( 5 );
203 texturesDataSources = new ArrayList<AbstractDataSource>( 5 );
204 textures = new HashMap<String, BufferedImage>( 10 );
205 textureExceptions = new HashMap<String, OGCWebServiceException>( 10 );
206 features = new HashMap<String, DefaultSurface>( 1000 );
207 // resultingJ3DScene = new BranchGroup();
208 resultingJ3DScene = null;
209 dgmType = ResolutionStripe.ELEVATION_MODEL_UNKNOWN;
210 }
211
212 /**
213 * @param surface
214 * of this resolution stripe, after the stripeFactory it is a trapezium, after the quad-splitter an
215 * axis-alligned bbox, which will be a request.
216 * @param maximumResolution
217 * the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
218 * viewer.
219 * @param minimumResolution
220 * the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
221 * @param minimalHeight
222 * the terrain height which will be taken if an error occurred while retrieving height data.
223 * @param outputFormat
224 * of the requests.
225 * @param scale
226 * of the heights ( 1.0 means no scaling )
227 */
228 public ResolutionStripe( Surface surface, double maximumResolution, double minimumResolution, double minimalHeight,
229 String outputFormat, double scale ) {
230 this( surface, maximumResolution, minimumResolution, minimalHeight, scale );
231 this.outputFormat = outputFormat;
232 }
233
234 /**
235 * @return the CRS of this ResolutionStripe
236 */
237 public CoordinateSystem getCRSName() {
238 return surface.getCoordinateSystem();
239 }
240
241 /**
242 * @return the largest resolution value, resulting in the smallest map Resolution, e.g. farthest away from the
243 * viewer.
244 */
245 public double getMaxResolution() {
246 return maxResolution;
247 }
248
249 /**
250 * @return the (always possitive) resolution of the largest (away from the viewer) side of the surface as scale
251 * denominator, which means divide by {@link MapUtils#DEFAULT_PIXEL_SIZE}.
252 */
253 public double getMaxResolutionAsScaleDenominator() {
254 return Math.abs( maxResolution ) / MapUtils.DEFAULT_PIXEL_SIZE;
255 }
256
257 /**
258 * @return the (always possitive) resolution of the smallest (towards the viewer) side of the surface as scale
259 * denominator, which means divide by {@link MapUtils#DEFAULT_PIXEL_SIZE}.
260 */
261 public double getMinResolutionAsScaleDenominator() {
262 return Math.abs( minResolution ) / MapUtils.DEFAULT_PIXEL_SIZE;
263 }
264
265 /**
266 * @return the smallest resolution value, resulting in the highest map Resolution, e.g. nearest to the viewer.
267 */
268 public double getMinResolution() {
269 return minResolution;
270 }
271
272 /**
273 * @return the geometric surface which is defines this resolutionStripe.
274 */
275 public Surface getSurface() {
276 return surface;
277 }
278
279 /**
280 * @return the minimalTerrainHeight.
281 */
282 public double getMinimalTerrainHeight() {
283 return minimalHeightlevel;
284 }
285
286 /**
287 * @return the requestwidth (in pixels) for the bbox containing this resolutionsstripe or -1 if the resolution is to
288 * high (e.g. int overload)
289 */
290 public int getRequestWidthForBBox() {
291 int result = (int) Math.round( surface.getEnvelope().getWidth() / Math.abs( minResolution ) );
292 if ( result > 8000 ) {
293 LOG.logDebug( "Returning -1 for the requestImageWidth, the maxResolution: " + maxResolution
294 + " the env.getHeight(): "
295 + surface.getEnvelope().getHeight() );
296 return -1;
297 }
298 return result;
299 }
300
301 /**
302 * @return the height (in pixels) of the request envelope or -1 if the resolution is to high
303 */
304 public int getRequestHeightForBBox() {
305 int result = (int) Math.round( surface.getEnvelope().getHeight() / Math.abs( minResolution ) );
306 if ( result > 80000 ) {
307 LOG.logDebug( "Returning -1 for the requestImageHeight, the maxResolution: " + maxResolution
308 + " the env.getHeight(): "
309 + surface.getEnvelope().getHeight() );
310 return -1;
311 }
312 return result;
313 }
314
315 /**
316 * @return the elevationModel if no elevationModel is created jet, an {@link TriangleTerrain} elevation model of the
317 * bbox of this stripe (with heightvalues set to minimalheigt) is returned.
318 */
319 public TerrainModel getElevationModel() {
320 if ( elevationModel == null ) {
321 elevationModel = createTriangleTerrainFromBBox();
322 }
323 return elevationModel;
324 }
325
326 /**
327 * @param elevationModel
328 * An other elevationModel.
329 */
330 public void setElevationModel( TerrainModel elevationModel ) {
331 this.elevationModel = elevationModel;
332 }
333
334 /**
335 * @param pointList
336 * containing Points which represents the heights of measures points (normally aquired from a wfs).
337 */
338 public void setElevationModelFromMeassurePoints( List<Point3d> pointList ) {
339 // LOG.logDebug( "Found following meassure points from a wfs: " + pointList );
340 this.pointList = pointList;
341 // A little hack.
342 // add the heightvalues of the nearest meassurepoint to the corner points of the bbox.
343 Point3d ll = new Point3d( Double.MAX_VALUE, Double.MAX_VALUE, 0 );
344 Point3d lr = new Point3d( Double.MIN_VALUE, Double.MAX_VALUE, 0 );
345 Point3d ur = new Point3d( Double.MIN_VALUE, Double.MIN_VALUE, 0 );
346 Point3d ul = new Point3d( Double.MAX_VALUE, Double.MIN_VALUE, 0 );
347 for ( Point3d p : pointList ) {
348 if ( p.x < ll.x && p.y < ll.y ) {
349 ll.x = p.x;
350 ll.y = p.y;
351 ll.z = p.z;
352 }
353 if ( p.x > lr.x && p.y < lr.y ) {
354 lr.x = p.x;
355 lr.y = p.y;
356 lr.z = p.z;
357 }
358 if ( p.x > ur.x && p.y > ur.y ) {
359 ur.x = p.x;
360 ur.y = p.y;
361 ur.z = p.z;
362 }
363 if ( p.x < ul.x && p.y > ul.y ) {
364 ul.x = p.x;
365 ul.y = p.y;
366 ul.z = p.z;
367 }
368 }
369 Position min = surface.getEnvelope().getMin();
370 Position max = surface.getEnvelope().getMax();
371 this.pointList.add( new Point3d( min.getX(), min.getY(), ll.z ) );
372 this.pointList.add( new Point3d( max.getX(), min.getY(), lr.z ) );
373 this.pointList.add( new Point3d( max.getX(), max.getY(), ur.z ) );
374 this.pointList.add( new Point3d( min.getX(), max.getY(), ul.z ) );
375 }
376
377 /**
378 * @param heightMap
379 * a BufferedImage which contains height values, normally aquired from a wcs.
380 */
381 public void setElevationModelFromHeightMap( BufferedImage heightMap ) {
382 Image2RawData i2rd = new Image2RawData( heightMap, (float) scale );
383 float[][] heights = i2rd.parse();
384
385 // smooth the outcome of the rasterdata, by applying a lowpass filter.
386 float[][] kernel = new float[5][5];
387 for ( int i = 0; i < kernel.length; i++ ) {
388 for ( int j = 0; j < kernel[i].length; j++ ) {
389 kernel[i][j] = 1;
390 }
391 }
392
393 try {
394 heights = Convolve.perform( heights, kernel );
395 } catch ( RasterFilterException e ) {
396 e.printStackTrace();
397 }
398
399 Envelope env = surface.getEnvelope();
400
401 Position lowerLeft = surface.getEnvelope().getMin();
402 Vector3f lLeft = new Vector3f( (float) lowerLeft.getX(), (float) lowerLeft.getY(), 0 );
403
404 // Triangles won't work -> an error in org.j3d.geom.terrain.ElevationGridGenerator therefor
405 // using TRIANGLE_STRIPS
406 LOG.logDebug( "Trying to create elevationmodel from the points received from a wcs" );
407 elevationModel = new TexturedHeightMapTerrain( (float) env.getWidth(),
408 (float) env.getHeight(),
409 heights,
410 lLeft,
411 GeometryData.TRIANGLE_STRIPS,
412 false );
413 LOG.logDebug( "From the point list of the wcs created following elevationModel: " + elevationModel );
414 }
415
416 /**
417 * @return the features of this resolutionstripe
418 */
419 public HashMap<String, DefaultSurface> getFeatures() {
420 return features;
421 }
422
423 /**
424 * @param key
425 * the name of the feature to be added.
426 * @param feature
427 * (e.g a building, tree etc.) as a DefaultSurface (derived frome Shape3D) to be added to the hashmap.
428 * @return true if the feature wasn't allready defined in the hashmap and could therefore be inserted, or if the key
429 * or feature are null.
430 */
431 public boolean addFeature( String key, DefaultSurface feature ) {
432 if ( feature != null && key != null ) {
433 DefaultSurface tmp = features.get( key );
434 if ( tmp == null && !features.containsKey( key ) ) {
435 features.put( key, feature );
436 return true;
437 }
438 }
439 return false;
440 }
441
442 /**
443 * @return the textures value.
444 */
445 public HashMap<String, BufferedImage> getTextures() {
446 return textures;
447 }
448
449 /**
450 * @param key
451 * the name of the texture to be added.
452 * @param texture
453 * to be added to the hashmap.
454 * @return true if the texture wasn't allready defined in the hashmap and could therefore be inserted, or if the key
455 * or texture are null.
456 */
457 public boolean addTexture( String key, BufferedImage texture ) {
458 if ( texture != null && key != null ) {
459 BufferedImage tmp = textures.get( key );
460 if ( tmp == null && !textures.containsKey( key ) ) {
461 textures.put( key, texture );
462 return true;
463 }
464 }
465 return false;
466 }
467
468 /**
469 * @return the elevationModelDataSource value.
470 */
471 public AbstractDataSource getElevationModelDataSource() {
472 return elevationModelDataSource;
473 }
474
475 /**
476 * @param elevationModelDataSource
477 * An other elevationModelDataSource value.
478 */
479 public void setElevationModelDataSource( AbstractDataSource elevationModelDataSource ) {
480 this.elevationModelDataSource = elevationModelDataSource;
481 if ( elevationModelDataSource.getServiceType() == AbstractDataSource.LOCAL_WFS || elevationModelDataSource.getServiceType() == AbstractDataSource.REMOTE_WFS ) {
482 dgmType = ResolutionStripe.ELEVATION_MODEL_POINTS;
483 } else if ( elevationModelDataSource.getServiceType() == AbstractDataSource.LOCAL_WCS || elevationModelDataSource.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
484 dgmType = ResolutionStripe.ELEVATION_MODEL_GRID;
485 }
486 }
487
488 /**
489 * @return the featureCollectionDataSources value.
490 */
491 public ArrayList<AbstractDataSource> getFeatureCollectionDataSources() {
492 return featureCollectionDataSources;
493 }
494
495 /**
496 * @param featureCollectionDataSource
497 * a DataSources for a specific featureCollection.
498 */
499 public void addFeatureCollectionDataSource( AbstractDataSource featureCollectionDataSource ) {
500 if ( featureCollectionDataSource != null ) {
501 if ( !featureCollectionDataSources.contains( featureCollectionDataSource ) ) {
502 featureCollectionDataSources.add( featureCollectionDataSource );
503 }
504 }
505 }
506
507 /**
508 * @return the texturesDataSources value.
509 */
510 public ArrayList<AbstractDataSource> getTexturesDataSources() {
511 return texturesDataSources;
512 }
513
514 /**
515 * @param textureDataSource
516 * An other texturesDataSources value.
517 */
518 public void addTextureDataSource( AbstractDataSource textureDataSource ) {
519 if ( textureDataSource != null ) {
520 if ( !texturesDataSources.contains( textureDataSource ) ) {
521 texturesDataSources.add( textureDataSource );
522 }
523 }
524 }
525
526 /**
527 *
528 * @return the OutputFormat of the resultImage
529 */
530 public String getOutputFormat() {
531 return outputFormat;
532 }
533
534 /**
535 * @param outputFormat
536 * the mime type of the resultimage
537 */
538 public void setOutputFormat( String outputFormat ) {
539 this.outputFormat = outputFormat;
540 }
541
542 /**
543 * After a call to this class call method, it is possible to get a Java3D representation --in form of a
544 * BranchGroup-- of this resolutionStripe. In this BranchGroup all the textures and requested features are added to
545 * the ElevationModel.
546 *
547 * @return a Java3D representation of this ResolutionStripe.
548 */
549 public OrderedGroup getJava3DRepresentation() {
550 if ( resultingJ3DScene == null ) {
551 createJava3DRepresentation();
552 }
553 return resultingJ3DScene;
554 }
555
556 @Override
557 public String toString() {
558 StringBuilder sb = new StringBuilder( 512 );
559 sb.append( "Resolution: " ).append( maxResolution ).append( "\n" );
560
561 try {
562 sb.append( "Surface: " ).append( WKTAdapter.export( this.surface ) ).append( "\n" );
563 } catch ( GeometryException e ) {
564 e.printStackTrace();
565 }
566 sb.append( "FeatureCollectionDataSources:\n " );
567 if ( featureCollectionDataSources.size() == 0 ) {
568 sb.append( " - No feature collection datasources defined.\n" );
569 } else {
570 for ( int i = 0; i < featureCollectionDataSources.size(); ++i ) {
571 sb.append( " - " )
572 .append( i )
573 .append( ") " )
574 .append( featureCollectionDataSources.get( i ) )
575 .append( "\n" );
576 }
577 }
578 sb.append( "TexturesDataSources:\n" );
579 if ( texturesDataSources.size() == 0 ) {
580 sb.append( " - No texture datasources defined.\n" );
581 } else {
582 for ( int i = 0; i < texturesDataSources.size(); ++i ) {
583 sb.append( " - " ).append( i ).append( ") " ).append( texturesDataSources.get( i ) ).append( "\n" );
584 }
585 }
586
587 sb.append( "ElevationDataSource: \n" );
588 if ( elevationModelDataSource == null ) {
589 sb.append( " - No elevation model datasources defined.\n" );
590 } else {
591 sb.append( " - " ).append( elevationModelDataSource ).append( "\n" );
592 }
593 return sb.toString();
594 }
595
596 /**
597 * @return a well known representation of the geometry of this Resolutionstripe
598 */
599 public String toWKT() {
600 try {
601 return new StringBuffer( WKTAdapter.export( this.surface ) ).toString();
602 } catch ( GeometryException e ) {
603 e.printStackTrace();
604 return new String( "" );
605 }
606 }
607
608 /**
609 * Outputs the textures to the tmp directory with following format:
610 * <code>key_response:___res:_maxresolution__random_id.jpg</code> this file will be deleted at jvm termination.
611 */
612 public void outputTextures() {
613
614 Set<String> keys = textures.keySet();
615 Random rand = new Random( System.currentTimeMillis() );
616 for ( String key : keys ) {
617 try {
618 // System.out.println( "saving image" );
619
620 File f = new File( key + "_response_" + rand.nextInt() + "__res_" + maxResolution + "___.jpg" );
621 f.deleteOnExit();
622 LOG.logDebug( "Saving result texture ( in file: " + f.getAbsolutePath()
623 + " for resolution stripe\n"
624 + this );
625 // System.out.println( f );
626 // ImageUtils.saveImage( responseImage, f, 1 );
627 ImageIO.write( textures.get( key ), "jpg", f );
628 } catch ( IOException e ) {
629 e.printStackTrace();
630 } catch ( Exception e ) {
631 e.printStackTrace();
632 }
633 }
634 }
635
636 /**
637 * @return the scale of the heights (1.0 means no scaling)
638 */
639 public double getScale() {
640 return scale;
641 }
642
643 /**
644 * @return one of the possible values {@link ResolutionStripe#ELEVATION_MODEL_UNKNOWN}
645 * {@link ResolutionStripe#ELEVATION_MODEL_GRID} {@link ResolutionStripe#ELEVATION_MODEL_POINTS}
646 */
647 public int getDGMType() {
648 return dgmType;
649 }
650
651 /**
652 * @return the resultTexture, which is build from all requested (wms/wcs) textures.
653 */
654 public BufferedImage getResultTexture() {
655 return resultTexture;
656 }
657
658 /**
659 * This call method is part of the Deegree Concurrent framework ({@link org.deegree.framework.concurrent.Executor}) .
660 * In this case it requests all the Data for a <code>ResolutionStripe</code> by invoking the necessary
661 * webservices.
662 *
663 * @see java.util.concurrent.Callable#call()
664 */
665 public ResolutionStripe call()
666 throws OGCWebServiceException {
667 int invokeCounter = 0;
668 // Strictly the different datasources must not be separated into two different
669 // DataSourceList, it might be handy (for caching) to do so though.
670 for ( AbstractDataSource textureDS : texturesDataSources ) {
671 invokeDataSource( textureDS, invokeCounter++ );
672 }
673 // create one texture from all textures
674 createTexture();
675
676 // Create the buildings etc.
677 for ( AbstractDataSource featureDS : featureCollectionDataSources ) {
678 invokeDataSource( featureDS, invokeCounter++ );
679 }
680
681 // create the terrain, if no terrain was requested, just create a flat square.
682 if ( elevationModelDataSource != null ) {
683 LOG.logDebug( "Invoking terrain datasource, because no elevation model was given." );
684 invokeDataSource( elevationModelDataSource, -1 );
685 } else {
686 LOG.logDebug( "Create flat triangle terrain, because no elevation model was given." );
687 elevationModel = createTriangleTerrainFromBBox();
688 }
689 // let this thread create the model in advance.
690 // if ( dgmType == ELEVATION_MODEL_GRID || dgmType == ELEVATION_MODEL_UNKNOWN) {
691 createJava3DRepresentation();
692 // }
693 return this;
694 }
695
696 private void invokeDataSource( AbstractDataSource ads, int id ) {
697 try {
698 GetViewServiceInvoker invoker = null;
699 if ( ads.getServiceType() == AbstractDataSource.LOCAL_WMS || ads.getServiceType() == AbstractDataSource.REMOTE_WMS ) {
700 invoker = new WMSInvoker( this, id );
701 } else if ( ads.getServiceType() == AbstractDataSource.LOCAL_WCS || ads.getServiceType() == AbstractDataSource.REMOTE_WCS ) {
702 invoker = new WCSInvoker( this, id, outputFormat, ( ads == elevationModelDataSource ) );
703 } else { // WFS -> was checked in DefaultGetViewHandler
704 invoker = new WFSInvoker( this, id, ( ads == elevationModelDataSource ) );
705 }
706 invoker.invokeService( ads );
707 } catch ( Throwable e ) {
708 if ( !Thread.currentThread().isInterrupted() ) {
709 LOG.logError( "WPVS: error while invoking a datasource: " + e.getMessage(), e );
710 }
711 }
712 }
713
714 private TriangleTerrain createTriangleTerrainFromBBox() {
715 List<Point3d> measurePoints = new ArrayList<Point3d>( 0 );
716 return new TriangleTerrain( measurePoints, surface.getEnvelope(), minimalHeightlevel, scale );
717 }
718
719 /**
720 * Creates a java3d representation of the data wihtin this resolutionstripe. If the DGM is defined from
721 * measurepoints, these points are triangualuated before createing the OrderedGroup.
722 */
723 private void createJava3DRepresentation() {
724 resultingJ3DScene = new OrderedGroup();
725
726 if ( resultTexture != null && elevationModel != null ) {
727 LOG.logDebug( "Creating an elevationmodel for the resolutionstripe" );
728 elevationModel.setTexture( resultTexture );
729 elevationModel.createTerrain();
730 resultingJ3DScene.addChild( elevationModel );
731 }
732
733 // add the features (e.g. buildings) to the j3d representation of this resolutionstripe.
734 Collection<DefaultSurface> featureSurfaces = features.values();
735 if ( featureSurfaces != null ) {
736 for ( DefaultSurface ds : featureSurfaces ) {
737 resultingJ3DScene.addChild( ds );
738 }
739 }
740 // if( LOG.getLevel() == ILogger.LOG_DEBUG ){
741 //
742 //
743 // try {
744 // File f = File.createTempFile( "features", ".xml" );
745 // J3DToCityGMLExporter exporter = new J3DToCityGMLExporter( "test", surface.getCoordinateSystem().getName(), "bla", f.getParent()+File.separator+"textures", 0, 0, 0, false );
746 // StringBuilder sb = new StringBuilder( 200000 );
747 // exporter.export( sb, resultingJ3DScene );
748 // LOG.logDebug( "Writing citygml file to: " + f.getAbsoluteFile() );
749 // BufferedWriter out = new BufferedWriter( new FileWriter( f ) );
750 // out.write( sb.toString() );
751 // out.flush();
752 // out.close();
753 // } catch ( IOException e ) {
754 // // TODO Auto-generated catch block
755 // e.printStackTrace();
756 // }
757 // }
758 }
759
760 /**
761 * Paints all textures on the given bufferedImage
762 */
763 private void createTexture() {
764 Collection<BufferedImage> textureImages = textures.values();
765 Graphics2D g2d = null;
766 if ( textureImages != null && !textureImages.isEmpty() ) {
767 // create texture as BufferedImage
768 if ( textureImages.size() > 0 ) {
769 Iterator<BufferedImage> it = textureImages.iterator();
770 resultTexture = it.next();
771 // get the g2d from the resulttexture in advance.
772 if ( resultTexture != null ) {
773 g2d = (Graphics2D) resultTexture.getGraphics();
774 }
775 while ( it.hasNext() ) {
776 if ( resultTexture == null ) {
777 resultTexture = it.next();
778 } else {
779 if ( g2d == null ) {
780 g2d = (Graphics2D) resultTexture.getGraphics();
781 }
782 // draw the next image on the g2d
783 g2d.drawImage( it.next(), 0, 0, null );
784 }
785 }
786 }
787 }
788 if ( resultTexture == null ) {
789 // no images were found, so output the error messages or the default error message.
790 LOG.logDebug( "No images were found (or all were null), therefore outputing the errormessages" );
791 resultTexture = new BufferedImage( getRequestWidthForBBox(),
792 getRequestHeightForBBox(),
793 BufferedImage.TYPE_INT_ARGB );
794 g2d = (Graphics2D) resultTexture.getGraphics();
795
796 if ( texturesDataSources.size() == 0 ) {
797 Collection<OGCWebServiceException> exceptions = textureExceptions.values();
798 String[] exceptionStrings = new String[exceptions.size()];
799 int count = 0;
800 for ( OGCWebServiceException ogcwse : exceptions ) {
801 String message = StringTools.concat( 100,
802 "error (",
803 Integer.valueOf( count + 1 ),
804 "): ",
805 ogcwse.getMessage() );
806 exceptionStrings[count++] = message;
807 }
808 paintString( g2d, exceptionStrings );
809 } else {
810 g2d.setColor( Color.WHITE );
811 g2d.drawRect( 0, 0, resultTexture.getWidth(), resultTexture.getHeight() );
812 }
813 }
814 if ( g2d != null ) {
815
816 if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
817 Stroke s = g2d.getStroke();
818 Color c = g2d.getColor();
819 g2d.setColor( Color.RED );
820 g2d.setStroke( new BasicStroke( 3 ) );
821 g2d.drawRect( 2, 2, resultTexture.getWidth() - 2, resultTexture.getHeight() - 2 );
822 g2d.setColor( c );
823 g2d.setStroke( s );
824
825 paintString( g2d, new String[] { Double.toString( minResolution ),
826 ( ( debugString != null ) ? debugString : "" ),
827 Double.toString( maxResolution ) } );
828 }
829 g2d.dispose();
830 }
831
832 }
833
834 private void paintString( Graphics2D g2d, String[] stringsToPaint ) {
835 Font originalFont = g2d.getFont();
836 float originalFontSize = originalFont.getSize();
837 if ( stringsToPaint == null || stringsToPaint.length == 0 ) {
838 LOG.logError( Messages.getMessage( "WPVS_NO_STRINGS" ) );
839 return;
840 }
841 // find the largest string
842 String testString = new String();
843 for ( int i = 0; i < stringsToPaint.length; ++i ) {
844 if ( stringsToPaint[i].length() > testString.length() ) {
845 testString = stringsToPaint[i];
846 }
847 }
848 // calculate the maximal height (with respect to the strings) of the font.
849 float requestWidth = getRequestWidthForBBox();
850 float requestHeight = getRequestHeightForBBox();
851 float maxFontHeight = requestHeight / stringsToPaint.length;
852 TextLayout tl = new TextLayout( testString, originalFont, g2d.getFontRenderContext() );
853 Rectangle2D r2d = tl.getBounds();
854 float width = (float) r2d.getWidth();
855 float height = (float) r2d.getHeight();
856
857 // little widther than the requestwidth ensures total readabillity
858 float approx = requestWidth / ( width * 1.2f );
859 if ( ( originalFontSize * approx ) < maxFontHeight ) {
860 originalFont = originalFont.deriveFont( originalFontSize * approx );
861 } else {
862 originalFont = originalFont.deriveFont( maxFontHeight );
863 }
864 tl = new TextLayout( testString, originalFont, g2d.getFontRenderContext() );
865 r2d = tl.getBounds();
866 width = (float) r2d.getWidth();
867 height = (float) r2d.getHeight();
868
869 int x = (int) Math.round( ( requestWidth * 0.5 ) - ( width * 0.5 ) );
870 int stringOffset = (int) Math.round( ( requestHeight * ( 1d / ( stringsToPaint.length + 1 ) ) ) + ( height * 0.5 ) );
871 g2d.setColor( Color.GRAY );
872 // g2d.drawRect( 0, 0, (int) requestWidth, (int) requestHeight );
873 g2d.setColor( Color.RED );
874 g2d.setFont( originalFont );
875 for ( int i = 0; i < stringsToPaint.length; ++i ) {
876 int y = ( i + 1 ) * stringOffset;
877 g2d.drawString( stringsToPaint[i], x, y );
878 }
879 }
880
881 /**
882 * @param datasourceID
883 * the id of the datasources which received an exception while retrieving a texture.
884 * @param exception
885 * the exception that occured while invoking the datasource.
886 */
887 public void setTextureRetrievalException( String datasourceID, OGCWebServiceException exception ) {
888 if ( datasourceID != null && exception != null ) {
889 if ( !textureExceptions.containsKey( datasourceID ) ) {
890 textureExceptions.put( datasourceID, exception );
891 }
892 }
893 }
894
895 /**
896 * @return the pointList
897 */
898 public final List<Point3d> getMeassurepointsAsList() {
899 return pointList;
900 }
901
902 /*
903 * (non-Javadoc)
904 *
905 * @see java.lang.Comparable#compareTo(java.lang.Object)
906 */
907 public int compareTo( Object o ) {
908 if ( o == null || o instanceof ResolutionStripe ) {
909 return 1;
910 }
911 ResolutionStripe other = (ResolutionStripe) o;
912 if ( Math.abs( other.maxResolution - this.maxResolution ) < 0.0001 ) {
913 return 0;
914 }
915 return ( ( this.maxResolution - other.maxResolution ) < 0 ) ? -1 : 1;
916 }
917
918 }