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