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