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