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 }