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 }