001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/tools/raster/RasterTreeUpdater.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 Aennchenstr. 19 030 53115 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.image.BufferedImage; 047 import java.io.File; 048 import java.io.IOException; 049 import java.net.MalformedURLException; 050 import java.net.URL; 051 import java.util.ArrayList; 052 import java.util.List; 053 import java.util.Properties; 054 import java.util.SortedMap; 055 import java.util.TreeMap; 056 057 import javax.media.jai.Interpolation; 058 import javax.media.jai.JAI; 059 import javax.media.jai.RenderedOp; 060 import javax.media.jai.TiledImage; 061 062 import net.sf.ehcache.Cache; 063 import net.sf.ehcache.CacheException; 064 import net.sf.ehcache.CacheManager; 065 import net.sf.ehcache.Element; 066 import net.sf.ehcache.store.MemoryStoreEvictionPolicy; 067 068 import org.deegree.framework.log.ILogger; 069 import org.deegree.framework.log.LoggerFactory; 070 import org.deegree.framework.util.ImageUtils; 071 import org.deegree.framework.util.StringTools; 072 import org.deegree.io.dbaseapi.DBaseException; 073 import org.deegree.io.shpapi.HasNoDBaseFileException; 074 import org.deegree.io.shpapi.ShapeFile; 075 import org.deegree.model.coverage.grid.WorldFile; 076 import org.deegree.model.crs.UnknownCRSException; 077 import org.deegree.model.feature.Feature; 078 import org.deegree.model.feature.FeatureProperty; 079 import org.deegree.model.spatialschema.Envelope; 080 import org.deegree.model.spatialschema.Geometry; 081 import org.deegree.ogcwebservices.wcs.configuration.Resolution; 082 import org.deegree.ogcwebservices.wcs.configuration.ShapeResolution; 083 import org.deegree.ogcwebservices.wcs.describecoverage.CoverageDescriptionDocument; 084 import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering; 085 import org.deegree.ogcwebservices.wcs.describecoverage.InvalidCoverageDescriptionExcpetion; 086 import org.xml.sax.SAXException; 087 088 import com.sun.media.jai.codec.FileSeekableStream; 089 090 /** 091 * The <code>RasterTreeUpdater</code> is a command line utility that can be used in addition to 092 * the <code>RasterTreeBuilder</code> to update a previously generated raster tree. 093 * 094 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> 095 * @author last edited by: $Author: apoth $ 096 * 097 * @version 2.0, $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 2007) $ 098 * 099 * @since 2.0 100 */ 101 public class RasterTreeUpdater { 102 103 private static final ILogger LOG = LoggerFactory.getLogger( RasterTreeUpdater.class ); 104 105 private RTUConfiguration config; 106 107 private SortedMap<Double, ShapeResolution> shapeFiles; 108 109 private Cache imgCache; 110 111 // is determined automatically off one of the output filenames 112 private String format; 113 114 /** 115 * Creates a new <code>RasterTreeUpdater</code> configured through the options contained in 116 * the passed configuration 117 * 118 * @param config 119 * @throws IllegalStateException 120 * @throws CacheException 121 * @throws IOException 122 */ 123 public RasterTreeUpdater( RTUConfiguration config ) throws IllegalStateException, CacheException, IOException { 124 this.config = config; 125 126 // a lot of lines just for a simple cache, but what the heck... 127 CacheManager singletonManager = CacheManager.create(); 128 if ( singletonManager.getCache( "imgCache" ) == null ) { 129 Cache cache = new Cache( "imgCache", 10, MemoryStoreEvictionPolicy.LFU, false, ".", false, 3600, 3600, 130 false, 240, null ); 131 singletonManager.addCache( cache ); 132 imgCache = singletonManager.getCache( "imgCache" ); 133 } else { 134 imgCache = singletonManager.getCache( "imgCache" ); 135 imgCache.removeAll(); 136 } 137 } 138 139 /** 140 * loads an image 141 * @param imageSource 142 * @return 143 * @throws IOException 144 */ 145 private TiledImage loadImage( String imageSource ) 146 throws IOException { 147 148 TiledImage ti = null; 149 Element elem = imgCache.get( imageSource ); 150 if ( elem != null ) { 151 ti = (TiledImage) elem.getObjectValue(); 152 } 153 154 if ( ti == null ) { 155 if ( config.verbose ) { 156 LOG.logInfo( "Cache size: " + imgCache.getSize() ); 157 LOG.logInfo( "Reading image: " + imageSource ); 158 } 159 160 FileSeekableStream fss = new FileSeekableStream( imageSource ); 161 RenderedOp rop = JAI.create( "stream", fss ); 162 BufferedImage bi = rop.getAsBufferedImage(); 163 fss.close(); 164 ti = new TiledImage( bi, 500, 500 ); 165 imgCache.put( new Element( imageSource, ti ) ); 166 } 167 168 return ti; 169 } 170 171 /** 172 * Initializes the instance. 173 * 174 * @throws IOException 175 * @throws SAXException 176 * @throws InvalidCoverageDescriptionExcpetion 177 * @throws UnknownCRSException 178 */ 179 public void init() 180 throws IOException, SAXException, InvalidCoverageDescriptionExcpetion, UnknownCRSException { 181 CoverageDescriptionDocument doc = new CoverageDescriptionDocument(); 182 doc.load( config.wcsConfiguration ); 183 184 CoverageOffering offering = null; 185 if ( config.coverageName == null ) { 186 offering = doc.getCoverageOfferings()[0]; 187 } else { 188 for ( CoverageOffering of : doc.getCoverageOfferings() ) { 189 if ( of.getName().equals( config.coverageName ) ) { 190 offering = of; 191 } 192 } 193 } 194 195 Resolution[] rs = offering.getExtension().getResolutions(); 196 shapeFiles = new TreeMap<Double, ShapeResolution>(); 197 for ( Resolution r : rs ) { 198 shapeFiles.put( new Double( r.getMinScale() ), (ShapeResolution) r ); 199 } 200 201 } 202 203 /** 204 * extracts the envelopes that correspond to the filenames of getfilenames 205 * @param shapeName 206 * @return 207 * @throws IOException 208 */ 209 private ArrayList<Envelope> getEnvelopes( String shapeName ) 210 throws IOException { 211 ShapeFile file = new ShapeFile( shapeName ); 212 ArrayList<Envelope> envs = new ArrayList<Envelope>( file.getRecordNum() ); 213 214 for ( int i = 0; i < file.getRecordNum(); ++i ) { 215 Geometry geom = file.getGeometryByRecNo( i + 1 ); 216 envs.add( geom.getEnvelope() ); 217 if ( config.verbose ) { 218 LOG.logInfo( StringTools.concat( 200, "Envelope of tile is ", geom.getEnvelope() ) ); 219 } 220 } 221 file.close(); 222 223 return envs; 224 } 225 226 /** 227 * extracts the filenames of the tiles contained within the shape file dbf 228 * @param shapeName 229 * @return 230 * @throws IOException 231 * @throws HasNoDBaseFileException 232 * @throws DBaseException 233 */ 234 private ArrayList<String> getTilenames( String shapeName ) 235 throws IOException, HasNoDBaseFileException, DBaseException { 236 ShapeFile file = new ShapeFile( shapeName ); 237 String dirName = new File( shapeName ).getParent(); 238 if ( dirName == null ) { 239 dirName = "./"; 240 } 241 242 ArrayList<String> tileNames = new ArrayList<String>( file.getRecordNum() ); 243 for ( int i = 0; i < file.getRecordNum(); ++i ) { 244 Feature f = file.getFeatureByRecNo( i + 1 ); 245 FeatureProperty[] p = f.getProperties(); 246 StringBuffer name = new StringBuffer( 200 ); 247 name.append( dirName ).append( "/" ); 248 name.append( ( p[1].getValue() == null ) ? "" : p[1].getValue() ); 249 name.append( "/" ).append( p[0].getValue() ); 250 tileNames.add( name.toString() ); 251 if ( config.verbose ) { 252 LOG.logInfo( StringTools.concat( 200, "Found tile ", name ) ); 253 } 254 } 255 file.close(); 256 257 return tileNames; 258 } 259 260 /** 261 * returns the envelopes of the files to be updated 262 * @return 263 * @throws IOException 264 */ 265 private ArrayList<Envelope> getUpdatedEnvelopes() 266 throws IOException { 267 ArrayList<Envelope> updatedEnvelopes = new ArrayList<Envelope>( config.updatedFiles.size() ); 268 269 for ( String filename : config.updatedFiles ) { 270 WorldFile wf = WorldFile.readWorldFile( filename, config.worldfileType ); 271 updatedEnvelopes.add( wf.getEnvelope() ); 272 if ( config.verbose ) { 273 LOG.logInfo( StringTools.concat( 200, "Updating from file ", filename, " with envelope ", 274 wf.getEnvelope() ) ); 275 } 276 if ( format == null ) { 277 format = filename.substring( filename.lastIndexOf( '.' ) + 1 ); 278 } 279 } 280 281 return updatedEnvelopes; 282 } 283 284 /** 285 * updates the tiles with the image file 286 * @param filename 287 * @param envelope 288 * @param tileNames 289 * @param tileEnvelopes 290 * @param res 291 * @throws IOException 292 */ 293 private void updateFile( String filename, Envelope envelope, List<String> tileNames, List<Envelope> tileEnvelopes, 294 double res ) 295 throws IOException { 296 297 for ( int i = 0; i < tileNames.size(); ++i ) { 298 Envelope env = tileEnvelopes.get( i ); 299 if ( !envelope.intersects( env ) ) { 300 continue; 301 } 302 String tile = tileNames.get( i ); 303 304 // paint the new image on top of the existing one 305 if ( config.verbose ) { 306 LOG.logInfo( StringTools.concat( 200, "Updating tile ", tile, " with image ", filename ) ); 307 } 308 309 TiledImage tileImage = loadImage( tile ); 310 WorldFile wf = WorldFile.readWorldFile( filename, config.worldfileType ); 311 TiledImage inputImage = loadImage( filename ); 312 Tile t = new Tile( WorldFile.readWorldFile( tile, config.worldfileType ).getEnvelope(), null ); 313 BufferedImage out = tileImage.getAsBufferedImage(); 314 float[][] data = null; 315 if ( out.getColorModel().getPixelSize() == 16 ) { 316 // do not use image api if target bitDepth = 16 317 data = new float[out.getHeight()][out.getWidth()]; 318 } 319 RasterTreeBuilder.drawImage( out, data, inputImage, t, wf, res, config.interpolation, null, format, 320 config.bitDepth, 0, 1 ); 321 322 String frm = format; 323 if ( "raw".equals( frm ) ) { 324 frm = "tif"; 325 } 326 327 File file = new File( tile ).getAbsoluteFile(); 328 329 ImageUtils.saveImage( out, file, config.quality ); 330 331 } 332 333 } 334 335 /** 336 * a hack to determine the minimum resolution 337 * @param shapeName 338 * @return 339 * @throws IOException 340 * @throws HasNoDBaseFileException 341 * @throws DBaseException 342 */ 343 private double getLevel( String shapeName ) 344 throws IOException, HasNoDBaseFileException, DBaseException { 345 ShapeFile file = new ShapeFile( shapeName ); 346 Feature f = file.getFeatureByRecNo( 1 ); 347 FeatureProperty[] p = f.getProperties(); 348 file.close(); 349 return Double.parseDouble( p[1].getValue().toString() ); 350 } 351 352 /** 353 * Updates the images. 354 * 355 * @throws IOException 356 * @throws DBaseException 357 * @throws HasNoDBaseFileException 358 */ 359 public void update() 360 throws IOException, HasNoDBaseFileException, DBaseException { 361 SortedMap<Double, ShapeResolution> shapes = new TreeMap<Double, ShapeResolution>(); 362 shapes.putAll( shapeFiles ); 363 364 // stores the envelopes of the files that are being updated 365 ArrayList<Envelope> updatedEnvelopes = getUpdatedEnvelopes(); 366 367 while ( !shapes.isEmpty() ) { 368 ShapeResolution shape = shapes.remove( shapes.firstKey() ); 369 String shapeName = shape.getShape().getRootFileName(); 370 double res = getLevel( shapeName ); 371 372 LOG.logInfo( StringTools.concat( 200, "Processing shape file ", shapeName, "..." ) ); 373 374 // these store the image filenames of the existing tiles and their envelopes 375 ArrayList<String> tileNames = getTilenames( shapeName ); 376 ArrayList<Envelope> envelopes = getEnvelopes( shapeName ); 377 378 for ( int i = 0; i < config.updatedFiles.size(); ++i ) { 379 String filename = config.updatedFiles.get( i ); 380 Envelope envelope = updatedEnvelopes.get( i ); 381 382 updateFile( filename, envelope, tileNames, envelopes, res ); 383 } 384 } 385 } 386 387 /** 388 * Prints out usage information and the message, then <code>System.exit</code>s. 389 * 390 * @param message 391 * can be null 392 */ 393 private static void printUsage( String message ) { 394 if ( message != null ) { 395 System.out.println( message ); 396 System.out.println(); 397 } 398 399 System.out.println( "Usage:" ); 400 System.out.println(); 401 System.out.println( "<classpath> <rtu> <options>" ); 402 System.out.println( " where" ); 403 System.out.println( " <rtu>:" ); 404 System.out.println( " java <classpath> org.deegree.tools.raster.RasterTreeUpdater" ); 405 System.out.println( " <classpath>:" ); 406 System.out.println( " -cp <the classpath containing the deegree.jar and " ); 407 System.out.println( " additional required libraries>" ); 408 System.out.println( " <option>:" ); 409 System.out.println( " as follows:" ); 410 System.out.println(); 411 System.out.println( " -wcs <URL/filename>:" ); 412 System.out.println( " The URL or a filename of the WCS configuration that was" ); 413 System.out.println( " generated by the RasterTreeBuilder. Mandatory." ); 414 System.out.println( " -name <name>:" ); 415 System.out.println( " The name of the coverage to update. Optional." ); 416 System.out.println( " -verbose:" ); 417 System.out.println( " Print out more informational messages." ); 418 System.out.println( " -interpolation <name>: " ); 419 System.out.println( " The name of the interpolation to be used, as specified in the" ); 420 System.out.println( " RasterTreeBuilder. Optional. Default is Nearest Neighbor." ); 421 System.out.println( " -depth <n>:" ); 422 System.out.println( " The bit depth of the output images. Optional. Default is 16." ); 423 System.out.println( " -quality <n>:" ); 424 System.out.println( " The desired output quality, between 0 and 1. Optional. Default is 0.95." ); 425 System.out.println( " -mapFiles <file1,file2...fileN>:" ); 426 System.out.println( " comma seperated list of image files to update. These files" ); 427 System.out.println( " need to have a corresponding worldfile, as usual." ); 428 System.out.println( " -worldFileType <type>:" ); 429 System.out.println( " How to treat worldfiles that are read. Possible values are outer and" ); 430 System.out.println( " center. Center is the default." ); 431 } 432 433 /** 434 * @param args 435 */ 436 public static void main( String[] args ) { 437 try { 438 RTUConfiguration config = new RTUConfiguration( args ); 439 RasterTreeUpdater updater = new RasterTreeUpdater( config ); 440 updater.init(); 441 updater.update(); 442 } catch ( MalformedURLException e ) { 443 e.printStackTrace(); 444 printUsage( "An URL is malformed." ); 445 } catch ( ClassCastException e ) { 446 e.printStackTrace(); 447 printUsage( "Data is not defined in shapefiles." ); 448 } catch ( IOException e ) { 449 e.printStackTrace(); 450 printUsage( "The coverage offering document can not be read:" ); 451 } catch ( SAXException e ) { 452 e.printStackTrace(); 453 printUsage( "The coverage offering document is not in XML format:" ); 454 } catch ( InvalidCoverageDescriptionExcpetion e ) { 455 e.printStackTrace(); 456 printUsage( "The coverage offering document is not valid:" ); 457 } catch ( UnknownCRSException e ) { 458 e.printStackTrace(); 459 printUsage( "The coverage offering document is not sound:" ); 460 } catch ( HasNoDBaseFileException e ) { 461 e.printStackTrace(); 462 printUsage( "A shapefile has no associated .dbf." ); 463 } catch ( DBaseException e ) { 464 e.printStackTrace(); 465 printUsage( "A shapefile database is in the wrong format or has errors." ); 466 } 467 468 } 469 470 /** 471 * <code>RTUConfiguration</code> is a class containing configuration options for the 472 * <code>RasterTreeUpdater</code>. 473 * 474 * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> 475 * @author last edited by: $Author: apoth $ 476 * 477 * @version 2.0, $Revision: 9346 $, $Date: 2007-12-27 17:39:07 +0100 (Do, 27 Dez 2007) $ 478 * 479 * @since 2.0 480 */ 481 public static class RTUConfiguration { 482 483 /** 484 * The location of the WCS configuration document. 485 */ 486 URL wcsConfiguration; 487 488 /** 489 * The list of image files being updated. 490 */ 491 List<String> updatedFiles; 492 493 /** 494 * The coverage name to update. 495 */ 496 String coverageName; 497 498 /** 499 * Whether to be verbose in logging. 500 */ 501 boolean verbose; 502 503 /** 504 * The interpolation method to be used. 505 */ 506 Interpolation interpolation; 507 508 /** 509 * The bit depth for the output images. 510 */ 511 int bitDepth; 512 513 /** 514 * Desired output image quality. 515 */ 516 float quality; 517 518 /** 519 * Worldfile type used for reading. 520 */ 521 WorldFile.TYPE worldfileType; 522 523 /** 524 * 525 * @param wcsConfiguration 526 * @param updatedFiles 527 * @param coverageName 528 * @param verbose 529 * @param interpolation 530 * @param bitDepth 531 * @param quality 532 * @param worldfileType 533 */ 534 public RTUConfiguration( URL wcsConfiguration, List<String> updatedFiles, String coverageName, boolean verbose, 535 Interpolation interpolation, int bitDepth, float quality, WorldFile.TYPE worldfileType ) { 536 this.wcsConfiguration = wcsConfiguration; 537 this.updatedFiles = updatedFiles; 538 this.coverageName = coverageName; 539 this.verbose = verbose; 540 this.interpolation = interpolation; 541 this.bitDepth = bitDepth; 542 this.quality = quality; 543 this.worldfileType = worldfileType; 544 } 545 546 /** 547 * Constructs a new instance through command line arguments. 548 * 549 * @param args 550 * the command line arguments 551 * @throws MalformedURLException 552 */ 553 public RTUConfiguration( String[] args ) throws MalformedURLException { 554 555 Properties map = new Properties(); 556 int i = 0; 557 while ( i < args.length ) { 558 if ( args[i].equals( "-verbose" ) ) { 559 map.put( args[i++], "-" ); 560 } else { 561 map.put( args[i++], args[i++] ); 562 } 563 } 564 565 try { 566 wcsConfiguration = new URL( map.getProperty( "-wcs" ) ); 567 } catch ( MalformedURLException e ) { 568 wcsConfiguration = new File( map.getProperty( "-wcs" ) ).toURI().toURL(); 569 } 570 571 coverageName = map.getProperty( "-name" ); 572 573 verbose = map.getProperty( "-verbose" ) != null; 574 575 if ( map.getProperty( "-interpolation" ) != null ) { 576 String t = map.getProperty( "-interpolation" ); 577 interpolation = RasterTreeBuilder.createInterpolation( t ); 578 } else { 579 interpolation = RasterTreeBuilder.createInterpolation( "Nearest Neighbor" ); 580 } 581 582 bitDepth = 32; 583 if ( map.getProperty( "-depth" ) != null ) { 584 bitDepth = Integer.parseInt( map.getProperty( "-depth" ) ); 585 } 586 587 quality = 0.95f; 588 if ( map.getProperty( "-quality" ) != null ) { 589 quality = Float.parseFloat( map.getProperty( "-quality" ) ); 590 } 591 592 worldfileType = WorldFile.TYPE.CENTER; 593 if ( map.getProperty( "-worldFileType" ) != null ) { 594 if ( map.getProperty( "-worldFileType" ).equalsIgnoreCase( "outer" ) ) { 595 worldfileType = WorldFile.TYPE.OUTER; 596 } 597 } 598 599 updatedFiles = StringTools.toList( map.getProperty( "-mapFiles" ), ",;", true ); 600 } 601 602 /** 603 * @return true, if the configuration values are sound 604 */ 605 public boolean isValidConfiguration() { 606 return updatedFiles.size() > 0 && wcsConfiguration != null; 607 } 608 609 } 610 611 }