001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/tools/raster/RasterTreeBuilder.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.tools.raster; 038 039 import static java.io.File.separator; 040 import static java.util.Arrays.sort; 041 042 import java.awt.Color; 043 import java.awt.Graphics; 044 import java.awt.Graphics2D; 045 import java.awt.GraphicsConfiguration; 046 import java.awt.GraphicsDevice; 047 import java.awt.GraphicsEnvironment; 048 import java.awt.RenderingHints; 049 import java.awt.color.ColorSpace; 050 import java.awt.geom.AffineTransform; 051 import java.awt.image.BufferedImage; 052 import java.awt.image.ColorModel; 053 import java.awt.image.ComponentColorModel; 054 import java.awt.image.DataBuffer; 055 import java.awt.image.Raster; 056 import java.awt.image.WritableRaster; 057 import java.io.File; 058 import java.io.FileOutputStream; 059 import java.io.FileWriter; 060 import java.io.FilenameFilter; 061 import java.io.IOException; 062 import java.io.InputStreamReader; 063 import java.io.PrintStream; 064 import java.io.PrintWriter; 065 import java.io.Reader; 066 import java.net.URI; 067 import java.net.URL; 068 import java.util.ArrayList; 069 import java.util.Arrays; 070 import java.util.Comparator; 071 import java.util.HashMap; 072 import java.util.Hashtable; 073 import java.util.Iterator; 074 import java.util.List; 075 import java.util.Map; 076 import java.util.Properties; 077 078 import javax.media.jai.JAI; 079 import javax.media.jai.RenderedOp; 080 import javax.media.jai.TiledImage; 081 082 import net.sf.ehcache.Cache; 083 import net.sf.ehcache.CacheManager; 084 import net.sf.ehcache.Element; 085 import net.sf.ehcache.store.MemoryStoreEvictionPolicy; 086 087 import org.deegree.datatypes.QualifiedName; 088 import org.deegree.datatypes.Types; 089 import org.deegree.framework.log.ILogger; 090 import org.deegree.framework.log.LoggerFactory; 091 import org.deegree.framework.util.ImageUtils; 092 import org.deegree.framework.util.StringTools; 093 import org.deegree.framework.xml.XMLFragment; 094 import org.deegree.framework.xml.XSLTDocument; 095 import org.deegree.graphics.transformation.GeoTransform; 096 import org.deegree.graphics.transformation.WorldToScreenTransform; 097 import org.deegree.io.dbaseapi.DBaseFile; 098 import org.deegree.io.shpapi.ShapeFile; 099 import org.deegree.model.coverage.grid.GridCoverageExchange; 100 import org.deegree.model.coverage.grid.WorldFile; 101 import org.deegree.model.crs.GeoTransformer; 102 import org.deegree.model.feature.Feature; 103 import org.deegree.model.feature.FeatureCollection; 104 import org.deegree.model.feature.FeatureFactory; 105 import org.deegree.model.feature.FeatureProperty; 106 import org.deegree.model.feature.schema.FeatureType; 107 import org.deegree.model.feature.schema.PropertyType; 108 import org.deegree.model.spatialschema.Envelope; 109 import org.deegree.model.spatialschema.Geometry; 110 import org.deegree.model.spatialschema.GeometryFactory; 111 import org.deegree.ogcbase.CommonNamespaces; 112 import org.deegree.processing.raster.converter.Image2RawData; 113 import org.deegree.processing.raster.converter.RawData2Image; 114 115 import com.sun.media.jai.codec.FileSeekableStream; 116 117 /** 118 * This class represents a <code>RasterTreeBuilder</code> object.<br> 119 * It wcan be used to create a resolution pyramid from one or more already existing raster dataset (image). The 120 * resulting pyramid will be described by a set of shapes (containing the image tiles bounding boxes) and a XML coverage 121 * description document that can be used with the deegree WCS. The RTB supports real images like png, tif, jpeg, bmp and 122 * gif as well as raw data image like 16Bit and 32Bit tif-images without color model. <br> 123 * because of the large amount of data that may be process by the RTB it makes use of a caching mechnism. For this the 124 * ehcache project is used. One can configure the cache behavior by placing a file named ehcache.xml defining a cache 125 * named 'imgCache' within the class root when starting the RTB. (For details please see the ehcache documentation). If 126 * no ehcache.xml is available default cache configuration will be used which is set to: 127 * <ul> 128 * <li>maxElementsInMemory = 10 129 * <li>memoryStoreEvictionPolicy = LFU 130 * <li>overflowToDisk = false (notice that overflow to disk is not supported because cached objects are not 131 * serializable) 132 * <li>eternal = false 133 * <li>timeToLiveSeconds = 3600 134 * <li>timeToIdleSeconds = 3600 135 * </ul> 136 * 137 * 138 * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a> 139 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 140 * @author last edited by: $Author: apoth $ 141 * 142 * @version 2.0, $Revision: 20078 $, $Date: 2009-10-09 11:40:15 +0200 (Fr, 09. Okt 2009) $ 143 * 144 * @since 2.0 145 */ 146 public class RasterTreeBuilder { 147 148 private static final ILogger LOG = LoggerFactory.getLogger( RasterTreeBuilder.class ); 149 150 private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" ); 151 152 private static final String APP_PREFIX = "app"; 153 154 // templates and transformation scripts 155 private URL configURL = RasterTreeBuilder.class.getResource( "template_wcs_configuration.xml" ); 156 157 private URL configXSL = RasterTreeBuilder.class.getResource( "updateConfig.xsl" ); 158 159 private URL inputXSL = RasterTreeBuilder.class.getResource( "updateCapabilities.xsl" ); 160 161 private int bitDepth = 16; 162 163 // input for new MergeRaste object 164 private List<String> imageFiles; 165 166 private List<WorldFile> imageFilesEnvs; 167 168 private Map<String, String> imageFilesErrors; 169 170 private String outputDir; 171 172 private String baseName; 173 174 private String outputFormat; 175 176 private double maxTileSize; 177 178 private String srs = null; 179 180 private Object interpolation = null; 181 182 private WorldFile.TYPE worldFileType = null; 183 184 private float quality = 0; 185 186 private String bgColor = null; 187 188 private float offset = 0; 189 190 private float scaleFactor = 1; 191 192 // minimum resolution of input images 193 private double minimumRes; 194 195 // combining image bounding box 196 private Envelope combiningEnvelope; 197 198 // size of virtual bounding box in px 199 private double pxWidthVirtualBBox; 200 201 private double pxHeightVirtualBBox; 202 203 // size of every tile in virtual bounding box in px 204 private long pxWidthTile; 205 206 private long pxHeightTile; 207 208 // number of tiles in virtual bounding box 209 private int tileRows; 210 211 private int tileCols; 212 213 private FeatureType ftype = null; 214 215 private FeatureCollection fc = null; 216 217 private Cache imgCache; 218 219 private boolean dummy; 220 221 /** 222 * @param imageFiles 223 * @param outputDir 224 * @param baseName 225 * @param outputFormat 226 * @param maxTileSize 227 * @param srs 228 * @param interpolation 229 * @param worldFileType 230 * @param quality 231 * @param bgColor 232 * @param depth 233 * @param resolution 234 * @param offset 235 * @param scaleFactor 236 * @param dummy 237 */ 238 public RasterTreeBuilder( List<String> imageFiles, String outputDir, String baseName, String outputFormat, 239 double maxTileSize, String srs, String interpolation, WorldFile.TYPE worldFileType, 240 float quality, String bgColor, int depth, double resolution, float offset, 241 float scaleFactor, boolean dummy ) { 242 this( imageFiles, outputDir, baseName, outputFormat, maxTileSize, srs, interpolation, worldFileType, quality, 243 bgColor, depth, resolution, offset, scaleFactor ); 244 this.dummy = dummy; 245 } 246 247 /** 248 * 249 * @param imageFiles 250 * @param outputDir 251 * @param baseName 252 * @param outputFormat 253 * @param maxTileSize 254 * @param srs 255 * @param interpolation 256 * @param worldFileType 257 * @param quality 258 * @param bgColor 259 * @param depth 260 * @param resolution 261 * @param offset 262 * @param scaleFactor 263 */ 264 public RasterTreeBuilder( List<String> imageFiles, String outputDir, String baseName, String outputFormat, 265 double maxTileSize, String srs, String interpolation, WorldFile.TYPE worldFileType, 266 float quality, String bgColor, int depth, double resolution, float offset, 267 float scaleFactor ) { 268 269 this.imageFiles = imageFiles; 270 this.imageFilesErrors = new HashMap<String, String>( imageFiles.size() ); 271 this.imageFilesEnvs = new ArrayList<WorldFile>( imageFiles.size() ); 272 for ( int i = 0; i < imageFiles.size(); i++ ) { 273 this.imageFilesEnvs.add( null ); 274 } 275 this.outputDir = outputDir; 276 File dir = new File( outputDir ).getAbsoluteFile(); 277 if ( !dir.exists() ) { 278 dir.mkdir(); 279 } 280 this.baseName = baseName; 281 this.outputFormat = outputFormat.toLowerCase(); 282 this.maxTileSize = maxTileSize; 283 this.srs = srs; 284 this.interpolation = createInterpolation( interpolation ); 285 this.worldFileType = worldFileType; 286 this.quality = quality; 287 this.bgColor = bgColor; 288 if ( depth != 0 ) { 289 this.bitDepth = depth; 290 } 291 this.minimumRes = resolution; 292 this.offset = offset; 293 this.scaleFactor = scaleFactor; 294 295 CacheManager singletonManager = CacheManager.create(); 296 if ( singletonManager.getCache( "imgCache" ) == null ) { 297 Cache cache = new Cache( "imgCache", 10, MemoryStoreEvictionPolicy.LFU, false, ".", false, 3600, 3600, 298 false, 240, null ); 299 singletonManager.addCache( cache ); 300 imgCache = singletonManager.getCache( "imgCache" ); 301 } else { 302 imgCache = singletonManager.getCache( "imgCache" ); 303 try { 304 imgCache.removeAll(); 305 } catch ( IOException e ) { 306 e.printStackTrace(); 307 } 308 } 309 310 PropertyType[] ftp = new PropertyType[3]; 311 ftp[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false ); 312 ftp[1] = FeatureFactory.createSimplePropertyType( 313 new QualifiedName( GridCoverageExchange.SHAPE_IMAGE_FILENAME ), 314 Types.VARCHAR, false ); 315 ftp[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ), 316 Types.VARCHAR, false ); 317 ftype = FeatureFactory.createFeatureType( new QualifiedName( "tiles" ), false, ftp ); 318 } 319 320 /** 321 * @throws IOException 322 */ 323 public void logCollectedErrors() 324 throws IOException { 325 FileOutputStream fos = new FileOutputStream( "RasterTreeBuilder" + minimumRes + ".log" ); 326 PrintWriter pw = new PrintWriter( fos ); 327 pw.println( "processing the following files caused an error" ); 328 Iterator<String> iter = imageFilesErrors.keySet().iterator(); 329 while ( iter.hasNext() ) { 330 String key = iter.next(); 331 String value = imageFilesErrors.get( key ); 332 pw.print( key ); 333 pw.print( ": " ); 334 pw.println( value ); 335 } 336 pw.close(); 337 LOG.logInfo( "LOG file RasterTreeBuilder.log has been written" ); 338 } 339 340 /** 341 * starts creating of a raster tile level using the current bbox and resolution 342 * 343 * @throws Exception 344 */ 345 public void start() 346 throws Exception { 347 System.gc(); 348 fc = FeatureFactory.createFeatureCollection( Double.toString( minimumRes ), tileRows * tileCols ); 349 createTiles( tileRows, tileCols ); 350 351 LOG.logInfo( "creating shape for georeferencing ... " ); 352 ShapeFile sf = new ShapeFile( outputDir + "/sh" + minimumRes, "rw" ); 353 sf.writeShape( fc ); 354 sf.close(); 355 356 } 357 358 /** 359 * @param env 360 * @param resolution 361 */ 362 public void init( Envelope env, double resolution ) { 363 364 // set target envelope 365 setEnvelope( env ); 366 setResolution( resolution ); 367 determineVirtualBBox(); 368 determineTileSize(); 369 } 370 371 /** 372 * sets the resolution level to be used for tiling 373 * 374 * @param resolution 375 */ 376 public void setResolution( double resolution ) { 377 minimumRes = resolution; 378 } 379 380 /** 381 * sets the bounding box used for tiling 382 * 383 * @param bbox 384 */ 385 public void setEnvelope( Envelope bbox ) { 386 combiningEnvelope = bbox; 387 } 388 389 /** 390 * TODO this is a copy from org.deegree.tools.raster#AutoTiler 391 * 392 * loads the base image 393 * 394 * @throws IOException 395 */ 396 private TiledImage loadImage( String imageSource ) 397 throws IOException { 398 399 TiledImage ti = null; 400 Element elem = imgCache.get( imageSource ); 401 if ( elem != null ) { 402 ti = (TiledImage) elem.getObjectValue(); 403 } 404 405 if ( ti == null ) { 406 System.out.println( "cache size: " + imgCache.getSize() ); 407 System.out.println( "read image: " + imageSource ); 408 409 FileSeekableStream fss = new FileSeekableStream( imageSource ); 410 RenderedOp rop = JAI.create( "stream", fss ); 411 BufferedImage bi = rop.getAsBufferedImage(); 412 try { 413 fss.close(); 414 } catch ( IOException e ) { 415 // should never happen 416 } 417 ti = new TiledImage( bi, 500, 500 ); 418 imgCache.put( new Element( imageSource, ti ) ); 419 } 420 421 return ti; 422 } 423 424 /** 425 * Determins the necessary size of a bounding box, which is large enough to hold all input image files. The result 426 * is stored in the combining <code>Envelope</code>. 427 * 428 * @throws Exception 429 */ 430 private WorldFile determineCombiningBBox() 431 throws Exception { 432 433 System.out.println( "calculating overall bounding box ..." ); 434 435 if ( imageFiles == null || imageFiles.isEmpty() ) { 436 throw new Exception( "No combining BoundingBox to be determined: " 437 + "The list of image files is null or empty." ); 438 } 439 440 WorldFile wf1 = null; 441 if ( combiningEnvelope == null ) { 442 443 // upper left corner of combining bounding box 444 double minX = Double.MAX_VALUE; 445 double maxY = Double.MIN_VALUE; 446 // lower right corner of combining bounding box 447 double maxX = Double.MIN_VALUE; 448 double minY = Double.MAX_VALUE; 449 // minimum resolution within combining bounding box 450 double minResX = Double.MAX_VALUE; 451 double minResY = Double.MAX_VALUE; 452 453 for ( int i = 0; i < imageFiles.size(); i++ ) { 454 455 File file = new File( imageFiles.get( i ) ); 456 if ( file.exists() && !file.isDirectory() ) { 457 System.out.println( imageFiles.get( i ) ); 458 FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) ); 459 RenderedOp rop = JAI.create( "stream", fss ); 460 int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue(); 461 int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue(); 462 fss.close(); 463 464 WorldFile wf = null; 465 466 try { 467 wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih ); 468 } catch ( Exception e ) { 469 LOG.logError( e.getMessage() ); 470 continue; 471 } 472 imageFilesEnvs.set( i, wf ); 473 // now the values of resx, resy, envelope of the current image 474 // (read from the world file) file are available 475 476 // find min for x and y 477 minX = Math.min( minX, wf.getEnvelope().getMin().getX() ); 478 minY = Math.min( minY, wf.getEnvelope().getMin().getY() ); 479 // find max for x and y 480 maxX = Math.max( maxX, wf.getEnvelope().getMax().getX() ); 481 maxY = Math.max( maxY, wf.getEnvelope().getMax().getY() ); 482 483 // find min for resolution of x and y 484 minResX = Math.min( minResX, wf.getResx() ); 485 minResY = Math.min( minResY, wf.getResy() ); 486 } else { 487 System.out.println( "File: " + imageFiles.get( i ) + " does not exist!" ); 488 System.out.println( "Image will be ignored" ); 489 } 490 if ( i % 10 == 0 ) { 491 System.gc(); 492 } 493 494 } 495 // store minimum resolution 496 if ( minimumRes <= 0 ) { 497 minimumRes = Math.min( minResX, minResY ); 498 } 499 combiningEnvelope = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null ); 500 LOG.logInfo( "determined envelope: ", combiningEnvelope ); 501 } 502 wf1 = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope ); 503 return wf1; 504 } 505 506 /** 507 * Determins a usefull size for the virtual bounding box. It is somewhat larger than the combining bounding box. The 508 * result is stored in the virtual <code>Envelope</code>. 509 * 510 */ 511 private Envelope determineVirtualBBox() { 512 513 double width = combiningEnvelope.getWidth(); 514 double height = combiningEnvelope.getHeight(); 515 516 // set width and height to next higher even-numbered thousand 517 double pxWidth = ( width / minimumRes ) + 1; 518 double pxHeight = ( height / minimumRes ) + 1; 519 520 pxWidthVirtualBBox = pxWidth; 521 pxHeightVirtualBBox = pxHeight; 522 523 // lower right corner of virtual bounding box 524 525 WorldFile wf = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope ); 526 // upper left corner of virtual bounding box 527 double minX = combiningEnvelope.getMin().getX(); 528 double maxY = combiningEnvelope.getMax().getY(); 529 530 double maxX = minX + ( ( pxWidth - 1 ) * wf.getResx() ); 531 double minY = maxY - ( ( pxHeight - 1 ) * wf.getResx() ); 532 533 return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null ); 534 535 // return combiningEnvelope; 536 } 537 538 /** 539 * This method determins and sets the size of the tiles in pixel both horizontally (pxWidthTile) and vertically 540 * (pxHeightTile). It also sets the necessary number of <code>tileCols</code> (depending on the tileWidth) and 541 * <code>tileRows</code> (depending on the tileHeight). 542 * 543 * By default, all tiles have a size of close to but less than 6000 pixel either way. 544 */ 545 private void determineTileSize() { 546 /* 547 * The size of the virtual bbox gets divided by maxTileSize to find an approximat number of tiles (a). 548 * 549 * If the virtual bbox is in any direction (horizontally or vertically) smaler than maxTileSize px, then it has 550 * only 1 tile in that direction. In this case, the size of the tile equals the size of the virtual bbox. 551 * 552 * Otherwise, divide the size of the pixel size of virtual bbox by the pixel tile size 553 */ 554 // determin width of tile 555 double a = ( pxWidthVirtualBBox / maxTileSize ); 556 int tileCols = (int) Math.ceil( a ); 557 if ( a <= 1.0 ) { 558 pxWidthTile = Math.round( pxWidthVirtualBBox ); 559 } else { 560 tileCols = (int) Math.round( ( pxWidthVirtualBBox / ( maxTileSize - 1 ) ) + 1 ); 561 pxWidthTile = (int) Math.round( maxTileSize ); 562 } 563 564 // determin height of tile 565 a = ( pxHeightVirtualBBox / maxTileSize ); 566 int tileRows = (int) Math.ceil( a ); 567 if ( a <= 1.0 ) { 568 pxHeightTile = Math.round( pxHeightVirtualBBox ); 569 } else { 570 tileRows = (int) Math.round( ( pxHeightVirtualBBox / ( maxTileSize - 1 ) ) + 1 ); 571 pxHeightTile = (int) Math.round( maxTileSize ); 572 } 573 574 this.tileCols = tileCols; 575 this.tileRows = tileRows; 576 577 LOG.logInfo( "minimum resolution: " + minimumRes ); 578 LOG.logInfo( "width = " + pxWidthVirtualBBox + " *** height = " + pxHeightVirtualBBox ); 579 LOG.logInfo( "pxWidthTile = " + pxWidthTile + " *** pxHeightTile = " + pxHeightTile ); 580 LOG.logInfo( "number of tiles: horizontally = " + tileCols + ", vertically = " + tileRows ); 581 } 582 583 /** 584 * Creates one <code>Tile</code> object after the other, with the number of tiles being specified by the given 585 * number of <code>rows</code> and <code>cols</code>. 586 * 587 * Each Tile gets written to the FileOutputStream by the internal call to #paintImagesOnTile. 588 * 589 * @param rows 590 * @param cols 591 * @throws IOException 592 * @throws Exception 593 */ 594 private void createTiles( int rows, int cols ) 595 throws IOException { 596 597 System.out.println( "creating merged image ..." ); 598 599 Envelope virtualEnv = determineVirtualBBox(); 600 601 double tileWidth = minimumRes * ( pxWidthTile ); 602 double tileHeight = minimumRes * ( pxHeightTile ); 603 604 double upperY = virtualEnv.getMax().getY(); 605 606 File file = new File( outputDir + '/' + Double.toString( minimumRes ) ).getAbsoluteFile(); 607 file.mkdir(); 608 609 for ( int i = 0; i < rows; i++ ) { 610 System.out.println( "processing row " + i ); 611 double leftX = virtualEnv.getMin().getX(); 612 double lowerY = upperY - tileHeight; 613 for ( int j = 0; j < cols; j++ ) { 614 615 System.out.println( "processing tile: " + i + " - " + j ); 616 double rightX = leftX + tileWidth; 617 Envelope env = GeometryFactory.createEnvelope( leftX, lowerY, rightX, upperY, null ); 618 leftX = rightX; 619 String postfix = "_" + i + "_" + j; 620 Tile tile = new Tile( env, postfix ); 621 622 paintImagesOnTile( tile ); 623 System.gc(); 624 } 625 upperY = lowerY; 626 } 627 System.gc(); 628 629 } 630 631 /** 632 * Paints all image files that intersect with the passed <code>tile</code> onto that tile and creates an output file 633 * in the <code>outputDir</code>. If no image file intersects with the given tile, then an empty output file is 634 * created. The name of the output file is defined by the <code>baseName</code> and the tile's index of row and 635 * column. 636 * 637 * @param tile 638 * The tile on which to paint the image. 639 * @throws IOException 640 */ 641 private void paintImagesOnTile( Tile tile ) 642 throws IOException { 643 644 Envelope tileEnv = tile.getTileEnvelope(); 645 String postfix = tile.getPostfix(); 646 647 BufferedImage out = createOutputImage(); 648 float[][] data = null; 649 if ( bitDepth == 16 && "raw".equals( outputFormat ) ) { 650 // do not use image api if target bitDepth = 16 651 data = new float[(int) pxHeightTile][(int) pxWidthTile]; 652 } 653 654 if ( bgColor != null ) { 655 Graphics g = out.getGraphics(); 656 g.setColor( Color.decode( bgColor ) ); 657 g.fillRect( 0, 0, out.getWidth(), out.getHeight() ); 658 g.dispose(); 659 } 660 boolean paint = false; 661 int gcc = 0; 662 663 if ( dummy ) { 664 paint = true; 665 } else { 666 for ( int i = 0; i < imageFiles.size(); i++ ) { 667 668 File file = new File( imageFiles.get( i ) ); 669 if ( imageFilesErrors.get( imageFiles.get( i ) ) == null && file.exists() && !file.isDirectory() ) { 670 671 WorldFile wf = imageFilesEnvs.get( i ); 672 if ( wf == null ) { 673 System.out.println( "read world file" ); 674 // just read image if bbox has not been already read 675 FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) ); 676 RenderedOp rop = JAI.create( "stream", fss ); 677 int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue(); 678 int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue(); 679 fss.close(); 680 try { 681 wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih ); 682 } catch ( Exception e ) { 683 imageFilesErrors.put( imageFiles.get( i ), e.getMessage() ); 684 continue; 685 } 686 // cache bounding boxes 687 imageFilesEnvs.set( i, wf ); 688 gcc++; 689 if ( gcc % 10 == 0 ) { 690 System.out.println( "garbage collecting" ); 691 System.gc(); 692 } 693 } 694 // now the values of resx, resy, envelope of the current image file are available 695 if ( wf.getEnvelope().intersects( tileEnv ) ) { 696 TiledImage bi = loadImage( imageFiles.get( i ) ); 697 gcc++; 698 try { 699 System.out.println( "drawImage" ); 700 drawImage( out, data, bi, tile, wf, minimumRes, interpolation, imageFilesErrors, 701 outputFormat, bitDepth, offset, scaleFactor ); 702 paint = true; 703 } catch ( Exception e ) { 704 e.printStackTrace(); 705 imageFilesErrors.put( imageFiles.get( i ), e.getMessage() ); 706 } 707 if ( gcc % 5 == 0 ) { 708 System.out.println( "garbage collecting" ); 709 System.gc(); 710 } 711 } 712 } else { 713 imageFilesErrors.put( imageFiles.get( i ), "image does not exist!" ); 714 } 715 } 716 } 717 if ( paint ) { 718 if ( !isTransparent( out ) ) { 719 // just write files if something has been painted 720 if ( bitDepth == 16 && "raw".equals( outputFormat ) ) { 721 out = RawData2Image.rawData2Image( data, false, scaleFactor, offset ); 722 } 723 storeTileImageToFileSystem( postfix, out ); 724 createWorldFile( tile ); 725 String frm = outputFormat; 726 if ( "raw".equals( outputFormat ) ) { 727 frm = "tif"; 728 } 729 storeEnvelope( Double.toString( minimumRes ), baseName + postfix + '.' + frm, tileEnv ); 730 } 731 } 732 } 733 734 /** 735 * creates an instance of a BufferedImage depending on requested target format 736 * 737 * @return the new image 738 */ 739 private BufferedImage createOutputImage() { 740 741 BufferedImage out = null; 742 if ( "jpg".equals( outputFormat ) || "jpeg".equals( outputFormat ) || "bmp".equals( outputFormat ) ) { 743 // for bmp, jpg, jpeg use 3 byte: 744 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_RGB ); 745 } else if ( "tif".equals( outputFormat ) || "tiff".equals( outputFormat ) || "png".equals( outputFormat ) 746 || "gif".equals( outputFormat ) ) { 747 // for tif, tiff and png use 4 byte: 748 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB ); 749 } else { 750 ColorModel ccm; 751 752 if ( bitDepth == 16 ) { 753 ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false, 754 BufferedImage.OPAQUE, DataBuffer.TYPE_USHORT ); 755 WritableRaster wr = ccm.createCompatibleWritableRaster( (int) pxWidthTile, (int) pxHeightTile ); 756 757 out = new BufferedImage( ccm, wr, false, new Hashtable<Object, Object>() ); 758 } else { 759 out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB ); 760 } 761 } 762 763 return out; 764 765 } 766 767 /** 768 * 769 * @param postfix 770 * tile name postfix ( -> tile index $x_$y ) 771 * @param out 772 * tile image to save 773 */ 774 private void storeTileImageToFileSystem( String postfix, BufferedImage out ) { 775 try { 776 String frm = outputFormat; 777 if ( "raw".equals( frm ) ) { 778 frm = "tif"; 779 } 780 String imageFile = outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + '.' + frm; 781 File file = new File( imageFile ).getAbsoluteFile(); 782 783 ImageUtils.saveImage( out, file, quality ); 784 785 } catch ( IOException e ) { 786 e.printStackTrace(); 787 } 788 } 789 790 /** 791 * Draws an image map to the target tile considering defined interpolation method for rescaling. This method is 792 * static so it can be used easily from the <code>RasterTreeUpdater</code>. 793 * 794 * @param out 795 * target image tile 796 * @param data 797 * @param image 798 * source image map 799 * @param tile 800 * tile description, must contain the envelope of the target image 801 * @param wf 802 * must contain the envelope of the TiledImage of the source image 803 * @param minimumRes 804 * the minimum resolution of input images 805 * @param interpolation 806 * the interpolation method 807 * @param imageFilesErrors 808 * a mapping between image files and errors 809 * @param outputFormat 810 * the output format 811 * @param bitDepth 812 * the output bit depth 813 * @param offset 814 * offset used if bitDepth = 16 and outputFormat = raw 815 * @param scaleFactor 816 * scale factor used if bitDepth = 16 and outputFormat = raw 817 */ 818 public static void drawImage( BufferedImage out, float[][] data, final TiledImage image, Tile tile, WorldFile wf, 819 double minimumRes, Object interpolation, Map<String, String> imageFilesErrors, 820 String outputFormat, int bitDepth, float offset, float scaleFactor ) { 821 822 Envelope tileEnv = tile.getTileEnvelope(); 823 Envelope mapEnv = wf.getEnvelope(); 824 825 GeoTransform gt2 = new WorldToScreenTransform( mapEnv.getMin().getX(), mapEnv.getMin().getY(), 826 mapEnv.getMax().getX(), mapEnv.getMax().getY(), 0, 0, 827 image.getWidth() - 1, image.getHeight() - 1 ); 828 829 Envelope inter = mapEnv.createIntersection( tileEnv ); 830 if ( inter == null ) 831 return; 832 int x1 = (int) Math.round( gt2.getDestX( inter.getMin().getX() ) ); 833 int y1 = (int) Math.round( gt2.getDestY( inter.getMax().getY() ) ); 834 int x2 = (int) Math.round( gt2.getDestX( inter.getMax().getX() ) ); 835 int y2 = (int) Math.round( gt2.getDestY( inter.getMin().getY() ) ); 836 837 if ( x2 - x1 >= 0 && y2 - y1 >= 0 && x1 + x2 - x1 < image.getWidth() && y1 + y2 - y1 < image.getHeight() 838 && x1 >= 0 && y1 >= 0 ) { 839 840 BufferedImage newImg = null; 841 int w = x2 - x1 + 1; 842 int h = y2 - y1 + 1; 843 // System.out.println( x1 + " " + y1 + " " + w + " " + h + " " + image.getWidth() + " " 844 // + image.getHeight() ); 845 BufferedImage img = image.getSubImage( x1, y1, w, h ).getAsBufferedImage(); 846 847 if ( !isTransparent( img ) ) { 848 849 // copy source image to a 4 Byte BufferedImage because there are 850 // problems with handling 8 Bit palette images 851 if ( img.getColorModel().getPixelSize() == 18 ) { 852 LOG.logInfo( "copy 8Bit image to 32Bit image" ); 853 BufferedImage bi = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB ); 854 855 Graphics g = bi.getGraphics(); 856 try { 857 g.drawImage( img, 0, 0, null ); 858 } catch ( Exception e ) { 859 System.out.println( e.getMessage() ); 860 } 861 g.dispose(); 862 img = bi; 863 } 864 if ( ( wf.getResx() / minimumRes < 0.9999 ) || ( wf.getResx() / minimumRes > 1.0001 ) 865 || ( wf.getResy() / minimumRes < 0.9999 ) || ( wf.getResy() / minimumRes > 1.0001 ) ) { 866 double dx = wf.getResx() / minimumRes; 867 double dy = wf.getResy() / minimumRes; 868 int destWidth = (int) Math.round( img.getWidth() * dx ); 869 int destHeight = (int) Math.round( img.getHeight() * dy ); 870 newImg = resize( img, destWidth, destHeight, interpolation ); 871 } else { 872 newImg = img; 873 } 874 GeoTransform gt = new WorldToScreenTransform( tileEnv.getMin().getX(), tileEnv.getMin().getY(), 875 tileEnv.getMax().getX(), tileEnv.getMax().getY(), 0, 0, 876 out.getWidth() - 1, out.getHeight() - 1 ); 877 878 x1 = (int) Math.round( gt.getDestX( inter.getMin().getX() ) ); 879 y1 = (int) Math.round( gt.getDestY( inter.getMax().getY() ) ); 880 x2 = (int) Math.round( gt.getDestX( inter.getMax().getX() ) ); 881 y2 = (int) Math.round( gt.getDestY( inter.getMin().getY() ) ); 882 883 if ( x2 - x1 > 0 && y2 - y1 > 0 ) { 884 // ensure that there is something to draw 885 try { 886 if ( "raw".equals( outputFormat ) ) { 887 DataBuffer outBuffer = out.getData().getDataBuffer(); 888 DataBuffer newImgBuffer = newImg.getData().getDataBuffer(); 889 int ps = newImg.getColorModel().getPixelSize(); 890 float[][] newData = null; 891 if ( bitDepth == 16 && ps == 16 ) { 892 Image2RawData i2r = new Image2RawData( newImg, 1f / scaleFactor, -1 * offset ); 893 // do not use image api if target bitDepth = 16 894 newData = i2r.parse(); 895 } 896 for ( int i = 0; i < newImg.getWidth(); i++ ) { 897 for ( int j = 0; j < newImg.getHeight(); j++ ) { 898 if ( x1 + i < out.getWidth() && y1 + j < out.getHeight() ) { 899 int newImgPos = newImg.getWidth() * j + i; 900 int outPos = out.getWidth() * ( y1 + j ) + ( x1 + i ); 901 if ( bitDepth == 16 && ps == 16 ) { 902 // int v = newImgBuffer.getElem( newImgPos ); 903 // outBuffer.setElem( outPos, v ); 904 data[y1 + j][x1 + i] = newData[j][i]; 905 } else if ( bitDepth == 16 && ps == 32 ) { 906 int v = newImg.getRGB( i, j ); 907 float f = Float.intBitsToFloat( v ) * 10f; 908 outBuffer.setElem( outPos, Math.round( f ) ); 909 // TODO 910 // data[y1 + j][x1 + i] = f; 911 } else if ( bitDepth == 32 && ps == 16 ) { 912 float f = newImgBuffer.getElem( newImgPos ) / 10f; 913 outBuffer.setElem( outPos, Float.floatToIntBits( f ) ); 914 } else { 915 out.setRGB( x1 + i, y1 + j, newImg.getRGB( i, j ) ); 916 } 917 } 918 } 919 } 920 if ( ( bitDepth == 16 && ps == 16 ) || ( bitDepth == 16 && ps == 32 ) 921 || ( bitDepth == 32 && ps == 16 ) ) { 922 out.setData( Raster.createRaster( out.getSampleModel(), outBuffer, null ) ); 923 } 924 } else { 925 Graphics g = out.getGraphics(); 926 g.drawImage( newImg, x1, y1, newImg.getWidth(), newImg.getHeight(), null ); 927 g.dispose(); 928 } 929 } catch ( Exception e ) { 930 LOG.logError( "Could not draw upon the image: " ); 931 LOG.logError( "New image is of size " + newImg.getWidth() + ", " + newImg.getHeight() ); 932 LOG.logError( "Position/width tried is (" + x1 + ", " + y1 + ", " + newImg.getWidth() + ", " 933 + newImg.getHeight() + ")" ); 934 if ( imageFilesErrors != null ) { 935 imageFilesErrors.put( tile.getPostfix(), StringTools.stackTraceToString( e ) ); 936 } 937 } 938 } 939 } 940 } 941 } 942 943 public static GraphicsConfiguration getDefaultConfiguration() { 944 GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment(); 945 GraphicsDevice gd = ge.getDefaultScreenDevice(); 946 return gd.getDefaultConfiguration(); 947 } 948 949 public static BufferedImage resize( BufferedImage source, int destWidth, int destHeight, Object interpolation ) { 950 951 int sourceWidth = source.getWidth(); 952 int sourceHeight = source.getHeight(); 953 double xScale = ( (double) destWidth ) / (double) sourceWidth; 954 double yScale = ( (double) destHeight ) / (double) sourceHeight; 955 if ( destWidth <= 0 ) { 956 xScale = yScale; 957 destWidth = (int) Math.rint( xScale * sourceWidth ); 958 } 959 if ( destHeight <= 0 ) { 960 yScale = xScale; 961 destHeight = (int) Math.rint( yScale * sourceHeight ); 962 } 963 GraphicsConfiguration gc = getDefaultConfiguration(); 964 BufferedImage result = gc.createCompatibleImage( destWidth, destHeight, 965 source.getColorModel().getTransparency() ); 966 Graphics2D g2d = null; 967 try { 968 g2d = result.createGraphics(); 969 g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, interpolation ); 970 AffineTransform at = AffineTransform.getScaleInstance( xScale, yScale ); 971 g2d.drawRenderedImage( source, at ); 972 } finally { 973 if ( g2d != null ) 974 g2d.dispose(); 975 } 976 return result; 977 } 978 979 private static boolean isTransparent( BufferedImage bi ) { 980 /* 981 * TODO determine if the passed image is completly transparent for ( int i = 0; i < bi.getHeight(); i++ ) { for 982 * ( int j = 0; j < bi.getWidth(); j++ ) { if ( bi.getRGB( i, j ) != 0 && bi.getRGB( i, j ) != -256 ) { return 983 * false; } } } return true; 984 */ 985 return false; 986 } 987 988 /** 989 * @return an interpolation object from a well known name 990 * @param interpolation 991 */ 992 public static Object createInterpolation( String interpolation ) { 993 Object interpol = null; 994 995 if ( interpolation.equalsIgnoreCase( "Nearest Neighbor" ) ) { 996 interpol = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR; 997 } else if ( interpolation.equalsIgnoreCase( "Bicubic" ) ) { 998 interpol = RenderingHints.VALUE_INTERPOLATION_BICUBIC; 999 } else if ( interpolation.equalsIgnoreCase( "Bicubic2" ) ) { 1000 // for downward compability 1001 interpol = RenderingHints.VALUE_INTERPOLATION_BICUBIC; 1002 } else if ( interpolation.equalsIgnoreCase( "Bilinear" ) ) { 1003 interpol = RenderingHints.VALUE_INTERPOLATION_BILINEAR; 1004 } else { 1005 throw new RuntimeException( "invalid interpolation method: " + interpolation ); 1006 } 1007 1008 return interpol; 1009 } 1010 1011 /** 1012 * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of the output file is 1013 * defined by the <code>baseName</code> and the tile's index of row and column. 1014 * 1015 * @param tile 1016 * The tile for which to create a world file. 1017 * @throws IOException 1018 */ 1019 private void createWorldFile( Tile tile ) 1020 throws IOException { 1021 1022 Envelope env = tile.getTileEnvelope(); 1023 String postfix = tile.getPostfix(); 1024 1025 StringBuffer sb = new StringBuffer( 1000 ); 1026 1027 sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 ); 1028 sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" ); 1029 sb.append( env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() ); 1030 sb.append( "\n" ); 1031 1032 File f = new File( outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + ".wld" ); 1033 1034 FileWriter fw = new FileWriter( f ); 1035 PrintWriter pw = new PrintWriter( fw ); 1036 1037 pw.print( sb.toString() ); 1038 1039 pw.close(); 1040 fw.close(); 1041 } 1042 1043 /** 1044 * stores an envelope and the assigend image file information into a feature/featureCollection 1045 * 1046 * @param dir 1047 * directory where the image file is stored 1048 * @param file 1049 * name of the image file 1050 * @param env 1051 * bbox of the image file 1052 */ 1053 private void storeEnvelope( String dir, String file, Envelope env ) { 1054 try { 1055 Geometry geom = GeometryFactory.createSurface( env, null ); 1056 FeatureProperty[] props = new FeatureProperty[3]; 1057 props[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), geom ); 1058 props[1] = FeatureFactory.createFeatureProperty( 1059 new QualifiedName( 1060 GridCoverageExchange.SHAPE_IMAGE_FILENAME ), 1061 file ); 1062 props[2] = FeatureFactory.createFeatureProperty( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ), 1063 dir ); 1064 Feature feat = FeatureFactory.createFeature( "file", ftype, props ); 1065 fc.add( feat ); 1066 } catch ( Exception e ) { 1067 e.printStackTrace(); 1068 } 1069 } 1070 1071 /** 1072 * creates a configuration file (extended CoverageDescriotion) for a WCS coverage considering the passed resolution 1073 * levels 1074 * 1075 * @param targetResolutions 1076 */ 1077 private void createConfigurationFile( double[] targetResolutions ) { 1078 1079 // copy this file to the target directory 1080 String resolutions = ""; 1081 sort( targetResolutions ); 1082 int length = targetResolutions.length; 1083 1084 for ( int i = 0; i < length; i++ ) { 1085 resolutions += String.valueOf( targetResolutions[length - 1 - i] ); 1086 if ( i < ( length - 1 ) ) 1087 resolutions += ','; 1088 } 1089 1090 try { 1091 Map<String, String> param = new HashMap<String, String>( 20 ); 1092 Envelope llEnv = getLatLonEnvelope( combiningEnvelope ); 1093 param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ',' 1094 + String.valueOf( llEnv.getMin().getY() ) ); 1095 param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ',' 1096 + String.valueOf( llEnv.getMax().getY() ) ); 1097 param.put( "upperleft", String.valueOf( combiningEnvelope.getMin().getX() ) + ',' 1098 + String.valueOf( combiningEnvelope.getMin().getY() ) ); 1099 param.put( "lowerright", String.valueOf( combiningEnvelope.getMax().getX() ) + ',' 1100 + combiningEnvelope.getMax().getY() ); 1101 File dir = new File( outputDir ); 1102 if ( dir.isAbsolute() ) { 1103 // param.put( "dataDir", outputDir + '/' ); 1104 param.put( "dataDir", "" ); 1105 } else { 1106 param.put( "dataDir", "" ); 1107 } 1108 param.put( "label", baseName ); 1109 param.put( "name", baseName ); 1110 param.put( "description", "" ); 1111 param.put( "keywords", "" ); 1112 param.put( "resolutions", resolutions ); 1113 String frm = outputFormat; 1114 if ( "raw".equals( outputFormat ) && bitDepth == 32 ) { 1115 frm = "tif"; 1116 } else if ( "raw".equals( outputFormat ) && bitDepth == 16 ) { 1117 frm = "GeoTiff"; 1118 } 1119 param.put( "mimeType", frm ); 1120 int p = srs.lastIndexOf( ':' ); 1121 param.put( "srs", srs.substring( p + 1, srs.length() ) ); 1122 param.put( "srsPre", srs.substring( 0, p + 1 ) ); 1123 1124 Reader reader = new InputStreamReader( configURL.openStream() ); 1125 1126 XSLTDocument xslt = new XSLTDocument(); 1127 xslt.load( configXSL ); 1128 XMLFragment xml = xslt.transform( reader, XMLFragment.DEFAULT_URL, null, param ); 1129 reader.close(); 1130 1131 // write the result 1132 String dstFilename = "wcs_" + baseName + "_configuration.xml"; 1133 File dstFile = new File( outputDir, dstFilename ); 1134 String configurationFilename = dstFile.getAbsolutePath().toString(); 1135 FileOutputStream fos = new FileOutputStream( configurationFilename ); 1136 xml.write( fos ); 1137 fos.close(); 1138 1139 } catch ( Exception e1 ) { 1140 e1.printStackTrace(); 1141 } 1142 1143 } 1144 1145 private Envelope getLatLonEnvelope( Envelope env ) 1146 throws Exception { 1147 GeoTransformer gt = new GeoTransformer( "EPSG:4326" ); 1148 return gt.transform( env, srs ); 1149 } 1150 1151 /** 1152 * 1153 */ 1154 private void updateCapabilitiesFile( File capabilitiesFile ) { 1155 1156 try { 1157 XSLTDocument xslt = new XSLTDocument(); 1158 xslt.load( inputXSL ); 1159 Map<String, String> param = new HashMap<String, String>(); 1160 1161 param.put( "dataDirectory", outputDir ); 1162 String url = new File( "wcs_" + baseName + "_configuration.xml" ).toURL().toString(); 1163 param.put( "configFile", url ); 1164 Envelope llEnv = getLatLonEnvelope( combiningEnvelope ); 1165 param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ',' 1166 + String.valueOf( llEnv.getMin().getY() ) ); 1167 param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ',' 1168 + String.valueOf( llEnv.getMax().getY() ) ); 1169 1170 param.put( "name", baseName ); 1171 param.put( "label", baseName ); 1172 1173 param.put( "description", "" ); 1174 param.put( "keywords", "" ); 1175 1176 XMLFragment xml = new XMLFragment(); 1177 xml.load( capabilitiesFile.toURL() ); 1178 1179 xml = xslt.transform( xml, capabilitiesFile.toURL().toExternalForm(), null, param ); 1180 1181 // write the result 1182 FileOutputStream fos = new FileOutputStream( capabilitiesFile ); 1183 xml.write( fos ); 1184 fos.close(); 1185 } catch ( Exception e ) { 1186 e.printStackTrace(); 1187 } 1188 } 1189 1190 /** 1191 * Validates the content of <code>map</code>, to see, if necessary arguments were passed when calling this class. 1192 * 1193 * @param map 1194 * @throws Exception 1195 */ 1196 private static void validate( Properties map ) 1197 throws Exception { 1198 1199 if ( map.get( "-outDir" ) == null ) { 1200 throw new Exception( "-outDir must be set" ); 1201 } 1202 String s = (String) map.get( "-outDir" ); 1203 if ( s.endsWith( "/" ) || s.endsWith( "\\" ) ) { 1204 s = s.substring( 0, s.length() - 1 ); 1205 } 1206 1207 if ( map.get( "-baseName" ) == null ) { 1208 throw new Exception( "-baseName must be set" ); 1209 } 1210 if ( map.get( "-outputFormat" ) == null ) { 1211 map.put( "-outputFormat", "png" ); 1212 } else { 1213 String format = ( (String) map.get( "-outputFormat" ) ).toLowerCase(); 1214 if ( !"bmp".equals( format ) && !"png".equals( format ) && !"jpg".equals( format ) 1215 && !"jpeg".equals( format ) && !"tif".equals( format ) && !"tiff".equals( format ) 1216 && !"gif".equals( format ) && !( "raw" ).equals( format ) ) { 1217 1218 throw new Exception( "-outputFormat must be one of the following: " 1219 + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'." ); 1220 } 1221 } 1222 if ( map.get( "-maxTileSize" ) == null ) { 1223 map.put( "-maxTileSize", "500" ); 1224 } 1225 if ( map.get( "-srs" ) == null ) { 1226 map.put( "-srs", "EPSG:4326" ); 1227 } 1228 if ( map.get( "-interpolation" ) == null ) { 1229 map.put( "-interpolation", "Nearest Neighbor" ); 1230 } 1231 if ( map.get( "-noOfLevel" ) == null ) { 1232 map.put( "-noOfLevel", "1" ); 1233 } 1234 if ( map.get( "-worldFileType" ) == null ) { 1235 map.put( "-worldFileType", "center" ); 1236 } 1237 if ( map.get( "-quality" ) == null ) { 1238 map.put( "-quality", "0.95" ); 1239 } 1240 if ( map.get( "-bbox" ) != null ) { 1241 double[] d = StringTools.toArrayDouble( (String) map.get( "-bbox" ), "," ); 1242 Envelope env = GeometryFactory.createEnvelope( d[0], d[1], d[2], d[3], null ); 1243 map.put( "-bbox", env ); 1244 if ( map.get( "-resolution" ) == null ) { 1245 throw new Exception( "-resolution must be set if -bbox is set" ); 1246 } 1247 map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) ); 1248 } else { 1249 if ( map.get( "-resolution" ) == null ) { 1250 map.put( "-resolution", new Double( -1 ) ); 1251 } else { 1252 map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) ); 1253 } 1254 } 1255 } 1256 1257 /** 1258 * @return the list of image map files to consider read from -mapFiles parameter 1259 * 1260 * @param mapFiles 1261 */ 1262 private static List<String> getFileList( String[] mapFiles ) { 1263 List<String> imageFiles = new ArrayList<String>(); 1264 for ( int i = 0; i < mapFiles.length; i++ ) { 1265 imageFiles.add( mapFiles[i] ); 1266 } 1267 return imageFiles; 1268 } 1269 1270 /** 1271 * @return the list of image map files to consider read from a defined root directory. 1272 * 1273 * @param rootDir 1274 * root directory where to read image map files 1275 * @param subdirs 1276 * true if subdirectories of the root directory shall be parsed for image maps too 1277 */ 1278 private static List<String> getFileList( String rootDir, boolean subdirs ) { 1279 List<String> list = new ArrayList<String>( 10000 ); 1280 File file = new File( rootDir ); 1281 String[] entries = file.list( new DFileFilter() ); 1282 if ( entries != null ) { 1283 for ( int i = 0; i < entries.length; i++ ) { 1284 File entry = new File( rootDir + '/' + entries[i] ); 1285 if ( entry.isDirectory() && subdirs ) { 1286 list = readSubDirs( entry, list ); 1287 } else { 1288 list.add( rootDir + '/' + entries[i] ); 1289 } 1290 } 1291 } 1292 return list; 1293 } 1294 1295 /** 1296 * 1297 * @param file 1298 * @param list 1299 * @return the sub directories 1300 */ 1301 private static List<String> readSubDirs( File file, List<String> list ) { 1302 1303 String[] entries = file.list( new DFileFilter() ); 1304 if ( entries != null ) { 1305 for ( int i = 0; i < entries.length; i++ ) { 1306 File entry = new File( file.getAbsolutePath() + '/' + entries[i] ); 1307 if ( entry.isDirectory() ) { 1308 list = readSubDirs( entry, list ); 1309 } else { 1310 list.add( file.getAbsolutePath() + '/' + entries[i] ); 1311 } 1312 } 1313 } 1314 return list; 1315 } 1316 1317 /** 1318 * @return the list of image map files to consider read from a dbase file defined by the dbase parameter 1319 * 1320 * @param dbaseFile 1321 * name of the dbase file 1322 * @param fileColumn 1323 * name of the column containing the image map files names 1324 * @param baseDir 1325 * name of the directory where the image map files are stored if this parameter is <code>null</code> it 1326 * is assumed that the image map files are full referenced within the dbase 1327 * @param sort 1328 * true if map image file names shall be sorted 1329 * @param sortColum 1330 * name of the column that shall be used for sorting 1331 */ 1332 private static List<String> getFileList( String dBaseFile, String fileColumn, String baseDir, boolean sort, 1333 String sortColum, String sortDirection ) 1334 throws Exception { 1335 1336 // handle dbase file extension and file location/reading problems 1337 if ( dBaseFile.endsWith( ".dbf" ) ) { 1338 dBaseFile = dBaseFile.substring( 0, dBaseFile.lastIndexOf( "." ) ); 1339 } 1340 DBaseFile dbf = new DBaseFile( dBaseFile ); 1341 1342 // sort dbase file contents chronologicaly (oldest first) 1343 int cnt = dbf.getRecordNum(); 1344 1345 Object[][] mapItems = new Object[cnt][2]; 1346 QualifiedName fileC = new QualifiedName( APP_PREFIX, fileColumn.toUpperCase(), DEEGREEAPP ); 1347 QualifiedName sortC = null; 1348 if ( sort ) { 1349 sortC = new QualifiedName( APP_PREFIX, sortColum.toUpperCase(), DEEGREEAPP ); 1350 } 1351 for ( int i = 0; i < cnt; i++ ) { 1352 if ( sort ) { 1353 mapItems[i][0] = dbf.getFRow( i + 1 ).getDefaultProperty( sortC ).getValue(); 1354 } else { 1355 mapItems[i][0] = new Integer( 1 ); 1356 } 1357 // name of map file 1358 mapItems[i][1] = dbf.getFRow( i + 1 ).getDefaultProperty( fileC ).getValue(); 1359 } 1360 Arrays.sort( mapItems, new MapAgeComparator( sortDirection ) ); 1361 1362 // extract names of image files from dBase file and attach them to rootDir 1363 if ( baseDir == null ) { 1364 baseDir = ""; 1365 } else if ( !baseDir.endsWith( "/" ) && !baseDir.endsWith( "\\" ) ) { 1366 baseDir = baseDir + "/"; 1367 } 1368 List<String> imageFiles = new ArrayList<String>( mapItems.length ); 1369 for ( int i = 0; i < mapItems.length; i++ ) { 1370 if ( mapItems[i][0] != null ) { 1371 LOG.logDebug( "" + mapItems[i][0] ); 1372 imageFiles.add( baseDir + mapItems[i][1] ); 1373 } 1374 } 1375 1376 return imageFiles; 1377 } 1378 1379 private static void printHelp() { 1380 1381 System.out.println( "-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n" 1382 + "-redirect whether to redirect the standard output/error streams to a file rtb.log in the output directory. Default is false.\r\n" 1383 + "-baseName base name used for creating names of the raster tile files. It also will be the name of the created coverage. (mandatory)\r\n" 1384 + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n" 1385 + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n" 1386 + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n" 1387 + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n" 1388 + " be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n" 1389 + " If you use the proogram with images (tif) containing raw data like DEMs just use \r\n" 1390 + " Nearest Neighbor interpolation. All other interpolation methods will cause artefacts." 1391 + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n" 1392 + "-resolution spatial resolution of the resulting coverage. If not set the resolution will determined by analysing the input map files. This parameter is conditional; if -bbox is defined -resolution must be defined too.\r\n" 1393 + "-noOfLevel number of tree levels created (optional default = 1)\r\n" 1394 + "-capabilitiesFile name of a deegree WCS capabilities/configuration file. If defined the program will add the created rastertree as a new coverage to the WCS configuration.\r\n" 1395 + "-h or -? print this help\r\n" 1396 + "\r\n" 1397 + "Input files\r\n" 1398 + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n" 1399 + "1)\r\n" 1400 + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n" 1401 + "\r\n" 1402 + "2)\r\n" 1403 + "-rootDir defines a directory that shall be parsed for files in a known image format. Each file found will be used as input.\r\n" 1404 + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n" 1405 + "\r\n" 1406 + "3)\r\n" 1407 + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n" 1408 + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n" 1409 + "-baseDir name of the directory where the files are stored. If this parameter will not be set the program assumes the -fileColumn contains completely referenced file names (optional)\r\n" 1410 + "-sortColumn If -dbaseFile is defined one can define a column that shall be used for sorting the files referenced by the -fileColumn (optional)\r\n" 1411 + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n" 1412 + "-worldFileType two types of are common: \r\n " 1413 + " a) the boundingbox is defined on the center of the corner pixels; \r\n " 1414 + " b) the boundingbox is defined on the outer corner of the corner pixels; \r\n " 1415 + " first is default and will be used if this parameter is not set; second will be use if '-worldFileType outer' is defined.\r\n" 1416 + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n" 1417 + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n" 1418 + "-bgColor defines the background color of the created tiles for those region no data are available (e.g. -bgColor 0xFFFFF defines background as white) \r\n" 1419 + " If no -bgColor is defined, transparent background will be used for image formats that are transparency enabled (e.g. png) and black is used for all other formats (e.g. bmp) \r\n" 1420 + "-offset defines the offset added to raster values if -outputFormat = raw and -bitDepth (default 0) \r\n" 1421 + "-scaleFactor defines the factor by which raster values are multiplied if -outputFormat = raw and -bitDepth (default 1) \r\n" 1422 + "\r\n" 1423 + "Common to all option defining the input files is that each referenced file must be in a known image format (png, tif, jpeg, bmp, gif) and if must be geo-referenced by a world file or must be a GeoTIFF." ); 1424 System.out.println(); 1425 System.out.println( "caching:" ); 1426 System.out.println( "To use default caching mechanism you just have to start RTB as before; to define " ); 1427 System.out.println( "your own caching behavior you have to place a file named ehcache.xml within the " ); 1428 System.out.println( "root of your classpath. The content of this file is described by the ehcache documentation " ); 1429 System.out.println( "(http://ehcache.sourceforge.net/documentation); at least it must provide a cache named 'imgCache'." ); 1430 System.out.println( " When defining your own cache please consider that just 'inMemory' caching is supported because " ); 1431 System.out.println( "the objects cached by RTB are not serializable." ); 1432 System.out.println(); 1433 System.out.println( "Example invoking RTB (windows):" ); 1434 System.out.println( "java -Xms300m -Xmx1000m -classpath .;.\\lib\\deegree2.jar;.\\lib\\acme.jar;" 1435 + ".\\lib\\batik-awt-util.jar;.\\lib\\commons-beanutils-1.5.jar;" 1436 + ".\\lib\\commons-codec-1.3.jar;.\\lib\\commons-collections-3.1.jar;" 1437 + ".\\lib\\commons-digester-1.7.jar;.\\lib\\commons-discovery-0.2.jar;" 1438 + ".\\lib\\commons-logging.jar;.\\lib\\jai_codec.jar;.\\lib\\jai_core.jar;" 1439 + ".\\lib\\mlibwrapper_jai.jar;.\\lib\\j3dcore.jar;.\\lib\\j3dutils.jar;" 1440 + ".\\lib\\vecmath.jar;.\\lib\\jts-1.6.jar;.\\lib\\log4j-1.2.9.jar;" 1441 + ".\\lib\\axis.jar;.\\lib\\jaxen-1.1-beta-7.jar;.\\lib\\ehcache-1.2.0_03.jar " 1442 + "org.deegree.tools.raster.RasterTreeBuilder " 1443 + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ " 1444 + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation " 1445 + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn " 1446 + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir " 1447 + "D:/lgv/resources/data/images/ " ); 1448 } 1449 1450 /** 1451 * 1452 * @param args 1453 * Example arguments to pass when calling are: 1454 * <ul> 1455 * <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li> 1456 * <li>-outDir D:/temp/out/</li> 1457 * <li>-baseName pretty</li> 1458 * <li>-outputFormat png</li> 1459 * <li>-maxTileSize 600</li> 1460 * </ul> 1461 * 1462 * @throws Exception 1463 */ 1464 public static void main( String[] args ) 1465 throws Exception { 1466 1467 Properties map = new Properties(); 1468 for ( int i = 0; i < args.length; i += 2 ) { 1469 map.put( args[i], args[i + 1] ); 1470 } 1471 1472 if ( map.get( "-?" ) != null || map.get( "-h" ) != null ) { 1473 printHelp(); 1474 return; 1475 } 1476 1477 try { 1478 validate( map ); 1479 } catch ( Exception e ) { 1480 LOG.logInfo( map.toString() ); 1481 System.out.println( e.getMessage() ); 1482 System.out.println(); 1483 printHelp(); 1484 return; 1485 } 1486 1487 String outDir = map.getProperty( "-outDir" ); 1488 1489 // set up stderr/stdout redirection 1490 String redirect = map.getProperty( "-redirect" ); 1491 if ( redirect != null && redirect.equals( "true" ) ) { 1492 File f = new File( outDir + separator + "rtb.log" ); 1493 PrintStream out = new PrintStream( new FileOutputStream( f ) ); 1494 System.setOut( out ); 1495 System.setErr( out ); 1496 } 1497 1498 // read input parameters 1499 String baseName = map.getProperty( "-baseName" ); 1500 String outputFormat = map.getProperty( "-outputFormat" ); 1501 String srs = map.getProperty( "-srs" ); 1502 if ( srs == null ) { 1503 srs = "EPSG:4326"; 1504 } 1505 String interpolation = map.getProperty( "-interpolation" ); 1506 Envelope env = (Envelope) map.get( "-bbox" ); 1507 double resolution = ( (Double) map.get( "-resolution" ) ).doubleValue(); 1508 int level = Integer.parseInt( map.getProperty( "-noOfLevel" ) ); 1509 double maxTileSize = ( Double.valueOf( map.getProperty( "-maxTileSize" ) ) ).doubleValue(); 1510 WorldFile.TYPE worldFileType = WorldFile.TYPE.CENTER; 1511 if ( "outer".equals( map.getProperty( "-worldFileType" ) ) ) { 1512 worldFileType = WorldFile.TYPE.OUTER; 1513 } 1514 float quality = Float.parseFloat( map.getProperty( "-quality" ) ); 1515 String backgroundColor = map.getProperty( "-bgColor" ); 1516 1517 int depth = 0; 1518 1519 if ( map.get( "-bitDepth" ) != null ) { 1520 depth = Integer.parseInt( map.getProperty( "-bitDepth" ) ); 1521 } 1522 1523 boolean dummy = false; 1524 if ( map.get( "-dummy" ) != null ) { 1525 dummy = true; 1526 } 1527 1528 float offset = 0; 1529 if ( map.get( "-offset" ) != null ) { 1530 offset = Float.parseFloat( map.getProperty( "-offset" ) ); 1531 } 1532 1533 float scaleFactor = 1; 1534 if ( map.get( "-scaleFactor" ) != null ) { 1535 scaleFactor = Float.parseFloat( map.getProperty( "-scaleFactor" ) ); 1536 } 1537 1538 List<String> imageFiles = null; 1539 if ( map.get( "-mapFiles" ) != null ) { 1540 String[] mapFiles = StringTools.toArray( map.getProperty( "-mapFiles" ), ",;|", true ); 1541 imageFiles = getFileList( mapFiles ); 1542 } else if ( map.get( "-dbaseFile" ) != null ) { 1543 String dBaseFile = map.getProperty( "-dbaseFile" ); 1544 String fileColum = map.getProperty( "-fileColumn" ); 1545 String baseDir = map.getProperty( "-baseDir" ); 1546 if ( baseDir == null ) { 1547 baseDir = map.getProperty( "-rootDir" ); 1548 } 1549 boolean sort = map.get( "-sortColumn" ) != null; 1550 String sortColumn = map.getProperty( "-sortColumn" ); 1551 if ( map.get( "-sortDirection" ) == null ) { 1552 map.put( "-sortDirection", "UP" ); 1553 } 1554 String sortDirection = map.getProperty( "-sortDirection" ); 1555 imageFiles = getFileList( dBaseFile, fileColum, baseDir, sort, sortColumn, sortDirection ); 1556 } else if ( map.get( "-rootDir" ) != null ) { 1557 String rootDir = map.getProperty( "-rootDir" ); 1558 boolean subDirs = "true".equals( map.get( "-subDirs" ) ); 1559 imageFiles = getFileList( rootDir, subDirs ); 1560 } else { 1561 LOG.logInfo( map.toString() ); 1562 System.out.println( "-mapFiles, -rootDir or -dbaseFile parameter must be defined" ); 1563 printHelp(); 1564 return; 1565 } 1566 1567 LOG.logDebug( imageFiles.toString() ); 1568 LOG.logInfo( map.toString() ); 1569 1570 // initialize RasterTreeBuilder 1571 RasterTreeBuilder rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs, 1572 interpolation, worldFileType, quality, backgroundColor, depth, 1573 resolution, offset, scaleFactor, dummy ); 1574 1575 // calculate bbox and resolution from input images if parameters are not set 1576 if ( env == null ) { 1577 WorldFile wf = rtb.determineCombiningBBox(); 1578 env = wf.getEnvelope(); 1579 resolution = wf.getResx(); 1580 } 1581 1582 // Calculate necessary number of levels to get not more than 4 1583 // tiles in highest resolution 1584 if ( level == -1 ) { 1585 rtb.init( env, resolution ); 1586 level = 0; 1587 int numTilesMax = Math.min( rtb.tileCols, rtb.tileRows ); 1588 int numTiles = 4; 1589 while ( numTiles < numTilesMax ) { 1590 level += 1; 1591 numTiles *= 2; 1592 } 1593 } 1594 if ( level == 0 ) { 1595 level = 1; 1596 } 1597 System.out.println( "Number of Levels: " + level ); 1598 1599 // create tree where for each loop resolution will be halfed 1600 double[] re = new double[level]; 1601 for ( int i = 0; i < level; i++ ) { 1602 rtb.init( env, resolution ); 1603 rtb.start(); 1604 rtb.logCollectedErrors(); 1605 re[i] = resolution; 1606 if ( i < level - 1 ) { 1607 String dir = outDir + '/' + Double.toString( resolution ); 1608 imageFiles = getFileList( dir, false ); 1609 rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs, 1610 interpolation, WorldFile.TYPE.CENTER, quality, backgroundColor, depth, 1611 resolution, offset, scaleFactor ); 1612 } 1613 resolution = resolution * 2; 1614 } 1615 1616 LOG.logInfo( "create configuration files ..." ); 1617 rtb.createConfigurationFile( re ); 1618 1619 if ( map.get( "-capabilitiesFile" ) != null ) { 1620 LOG.logInfo( "adjust capabilities ..." ); 1621 File file = new File( map.getProperty( "-capabilitiesFile" ) ); 1622 rtb.updateCapabilitiesFile( file ); 1623 } 1624 1625 rtb.logCollectedErrors(); 1626 } 1627 1628 /** 1629 * class: official version of a FilenameFilter 1630 */ 1631 static class DFileFilter implements FilenameFilter { 1632 1633 private List<String> extensions = null; 1634 1635 /** 1636 * 1637 */ 1638 public DFileFilter() { 1639 extensions = new ArrayList<String>(); 1640 extensions.add( "JPEG" ); 1641 extensions.add( "JPG" ); 1642 extensions.add( "BMP" ); 1643 extensions.add( "PNG" ); 1644 extensions.add( "GIF" ); 1645 extensions.add( "TIF" ); 1646 extensions.add( "TIFF" ); 1647 extensions.add( "GEOTIFF" ); 1648 } 1649 1650 /** 1651 * @return "*.*" 1652 */ 1653 public String getDescription() { 1654 return "*.*"; 1655 } 1656 1657 /* 1658 * (non-Javadoc) 1659 * 1660 * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) 1661 */ 1662 public boolean accept( java.io.File file, String name ) { 1663 int pos = name.lastIndexOf( "." ); 1664 String ext = name.substring( pos + 1 ).toUpperCase(); 1665 if ( file.isDirectory() ) { 1666 String s = file.getAbsolutePath() + '/' + name; 1667 File tmp = new File( s ); 1668 if ( tmp.isDirectory() ) { 1669 return true; 1670 } 1671 } 1672 return extensions.contains( ext ); 1673 } 1674 } 1675 1676 /** 1677 * 1678 * This class enables sorting of dBaseFile objects in chronological order (lowest first, highest last). 1679 * 1680 * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a> 1681 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 1682 * @author last edited by: $Author: apoth $ 1683 * 1684 * @version 2.0, $Revision: 20078 $, $Date: 2009-10-09 11:40:15 +0200 (Fr, 09. Okt 2009) $ 1685 * 1686 * @since 2.0 1687 */ 1688 private static class MapAgeComparator implements Comparator<Object> { 1689 1690 private String direction = null; 1691 1692 /** 1693 * @param direction 1694 */ 1695 public MapAgeComparator( String direction ) { 1696 this.direction = direction.toUpperCase(); 1697 } 1698 1699 public int compare( Object o1, Object o2 ) { 1700 Object[] o1a = (Object[]) o1; 1701 Object[] o2a = (Object[]) o2; 1702 1703 if ( o1a[0] == null || o2a[0] == null ) { 1704 return 0; 1705 } 1706 if ( direction.equals( "UP" ) ) { 1707 return o1a[0].toString().compareTo( o2a[0].toString() ); 1708 } 1709 return o2a[0].toString().compareTo( o1a[0].toString() ); 1710 } 1711 } 1712 1713 }