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