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