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