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 }