001    //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/tools/raster/RasterTreeBuilder.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     Aennchenstraße 19
030     53177 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 static java.io.File.separator;
047    import static java.util.Arrays.sort;
048    
049    import java.awt.Color;
050    import java.awt.Graphics;
051    import java.awt.color.ColorSpace;
052    import java.awt.image.BufferedImage;
053    import java.awt.image.ColorModel;
054    import java.awt.image.ComponentColorModel;
055    import java.awt.image.DataBuffer;
056    import java.awt.image.Raster;
057    import java.awt.image.WritableRaster;
058    import java.awt.image.renderable.ParameterBlock;
059    import java.io.File;
060    import java.io.FileOutputStream;
061    import java.io.FileWriter;
062    import java.io.FilenameFilter;
063    import java.io.IOException;
064    import java.io.InputStreamReader;
065    import java.io.PrintStream;
066    import java.io.PrintWriter;
067    import java.io.Reader;
068    import java.net.URI;
069    import java.net.URL;
070    import java.util.ArrayList;
071    import java.util.Arrays;
072    import java.util.Comparator;
073    import java.util.HashMap;
074    import java.util.Hashtable;
075    import java.util.Iterator;
076    import java.util.List;
077    import java.util.Map;
078    import java.util.Properties;
079    
080    import javax.media.jai.Interpolation;
081    import javax.media.jai.InterpolationBicubic;
082    import javax.media.jai.InterpolationBicubic2;
083    import javax.media.jai.InterpolationBilinear;
084    import javax.media.jai.InterpolationNearest;
085    import javax.media.jai.JAI;
086    import javax.media.jai.RenderedOp;
087    import javax.media.jai.TiledImage;
088    
089    import net.sf.ehcache.Cache;
090    import net.sf.ehcache.CacheManager;
091    import net.sf.ehcache.Element;
092    import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
093    
094    import org.deegree.datatypes.QualifiedName;
095    import org.deegree.datatypes.Types;
096    import org.deegree.framework.log.ILogger;
097    import org.deegree.framework.log.LoggerFactory;
098    import org.deegree.framework.util.ImageUtils;
099    import org.deegree.framework.util.StringTools;
100    import org.deegree.framework.xml.XMLFragment;
101    import org.deegree.framework.xml.XSLTDocument;
102    import org.deegree.graphics.transformation.GeoTransform;
103    import org.deegree.graphics.transformation.WorldToScreenTransform;
104    import org.deegree.io.dbaseapi.DBaseFile;
105    import org.deegree.io.shpapi.ShapeFile;
106    import org.deegree.model.coverage.grid.GridCoverageExchange;
107    import org.deegree.model.coverage.grid.WorldFile;
108    import org.deegree.model.crs.GeoTransformer;
109    import org.deegree.model.feature.Feature;
110    import org.deegree.model.feature.FeatureCollection;
111    import org.deegree.model.feature.FeatureFactory;
112    import org.deegree.model.feature.FeatureProperty;
113    import org.deegree.model.feature.schema.FeatureType;
114    import org.deegree.model.feature.schema.PropertyType;
115    import org.deegree.model.spatialschema.Envelope;
116    import org.deegree.model.spatialschema.Geometry;
117    import org.deegree.model.spatialschema.GeometryFactory;
118    import org.deegree.ogcbase.CommonNamespaces;
119    import org.deegree.processing.raster.converter.Image2RawData;
120    import org.deegree.processing.raster.converter.RawData2Image;
121    
122    import com.sun.media.jai.codec.FileSeekableStream;
123    
124    /**
125     * This class represents a <code>RasterTreeBuilder</code> object.<br>
126     * It wcan be used to create a resolution pyramid from one or more already existing raster dataset
127     * (image). The resulting pyramid will be described by a set of shapes (containing the image tiles
128     * bounding boxes) and a XML coverage description document that can be used with the deegree WCS.
129     * The RTB supports real images like png, tif, jpeg, bmp and gif as well as raw data image like
130     * 16Bit and 32Bit tif-images without color model. <br>
131     * because of the large amount of data that may be process by the RTB it makes use of a caching
132     * mechnism. For this the ehcache project is used. One can configure the cache behavior by placing a
133     * file named ehcache.xml defining a cache named 'imgCache' within the class root when starting the
134     * RTB. (For details please see the ehcache documentation). If no ehcache.xml is available default
135     * cache configuration will be used which is set to:
136     * <ul>
137     * <li>maxElementsInMemory = 10
138     * <li>memoryStoreEvictionPolicy = LFU
139     * <li>overflowToDisk = false (notice that overflow to disk is not supported because cached objects
140     * are not serializable)
141     * <li>eternal = false
142     * <li>timeToLiveSeconds = 3600
143     * <li>timeToIdleSeconds = 3600
144     * </ul>
145     * 
146     * 
147     * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
148     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
149     * @author last edited by: $Author: apoth $
150     * 
151     * @version 2.0, $Revision: 9681 $, $Date: 2008-01-23 09:55:12 +0100 (Mi, 23 Jan 2008) $
152     * 
153     * @since 2.0
154     */
155    public class RasterTreeBuilder {
156    
157        private static final ILogger LOG = LoggerFactory.getLogger( RasterTreeBuilder.class );
158    
159        private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" );
160    
161        private static final String APP_PREFIX = "app";
162    
163        // templates and transformation scripts
164        private URL configURL = RasterTreeBuilder.class.getResource( "template_wcs_configuration.xml" );
165    
166        private URL configXSL = RasterTreeBuilder.class.getResource( "updateConfig.xsl" );
167    
168        private URL inputXSL = RasterTreeBuilder.class.getResource( "updateCapabilities.xsl" );
169    
170        private int bitDepth = 16;
171    
172        // input for new MergeRaste object
173        private List<String> imageFiles;
174    
175        private List<WorldFile> imageFilesEnvs;
176    
177        private Map<String, String> imageFilesErrors;
178    
179        private String outputDir;
180    
181        private String baseName;
182    
183        private String outputFormat;
184    
185        private double maxTileSize;
186    
187        private String srs = null;
188    
189        private Interpolation interpolation = null;
190    
191        private WorldFile.TYPE worldFileType = null;
192    
193        private float quality = 0;
194    
195        private String bgColor = null;
196    
197        private float offset = 0;
198    
199        private float scaleFactor = 1;
200    
201        // minimum resolution of input images
202        private double minimumRes;
203    
204        // combining image bounding box
205        private Envelope combiningEnvelope;
206    
207        // size of virtual bounding box in px
208        private long pxWidthVirtualBBox;
209    
210        private long pxHeightVirtualBBox;
211    
212        // size of every tile in virtual bounding box in px
213        private long pxWidthTile;
214    
215        private long pxHeightTile;
216    
217        // number of tiles in virtual bounding box
218        private int tileRows;
219    
220        private int tileCols;
221    
222        private FeatureType ftype = null;
223    
224        private FeatureCollection fc = null;
225    
226        private Cache imgCache;
227    
228        /**
229         * 
230         * @param imageFiles
231         * @param outputDir
232         * @param baseName
233         * @param outputFormat
234         * @param maxTileSize
235         * @param srs
236         * @param interpolation
237         * @param worldFileType
238         * @param quality
239         * @param bgColor
240         * @param depth
241         * @param resolution
242         * @param offset
243         * @param scaleFactor
244         */
245        public RasterTreeBuilder( List<String> imageFiles, String outputDir, String baseName, String outputFormat,
246                                  double maxTileSize, String srs, String interpolation, WorldFile.TYPE worldFileType,
247                                  float quality, String bgColor, int depth, double resolution, float offset,
248                                  float scaleFactor ) {
249    
250            this.imageFiles = imageFiles;
251            this.imageFilesErrors = new HashMap<String, String>( imageFiles.size() );
252            this.imageFilesEnvs = new ArrayList<WorldFile>( imageFiles.size() );
253            for ( int i = 0; i < imageFiles.size(); i++ ) {
254                this.imageFilesEnvs.add( null );
255            }
256            this.outputDir = outputDir;
257            File dir = new File( outputDir ).getAbsoluteFile();
258            if ( !dir.exists() ) {
259                dir.mkdir();
260            }
261            this.baseName = baseName;
262            this.outputFormat = outputFormat.toLowerCase();
263            this.maxTileSize = maxTileSize;
264            this.srs = srs;
265            this.interpolation = createInterpolation( interpolation );
266            this.worldFileType = worldFileType;
267            this.quality = quality;
268            this.bgColor = bgColor;
269            if ( depth != 0 ) {
270                this.bitDepth = depth;
271            }
272            this.minimumRes = resolution;
273            this.offset = offset;
274            this.scaleFactor = scaleFactor;
275    
276            CacheManager singletonManager = CacheManager.create();
277            if ( singletonManager.getCache( "imgCache" ) == null ) {
278                Cache cache = new Cache( "imgCache", 10, MemoryStoreEvictionPolicy.LFU, false, ".", false, 3600, 3600,
279                                         false, 240, null );
280                singletonManager.addCache( cache );
281                imgCache = singletonManager.getCache( "imgCache" );
282            } else {
283                imgCache = singletonManager.getCache( "imgCache" );
284                try {
285                    imgCache.removeAll();
286                } catch ( IOException e ) {
287                    e.printStackTrace();
288                }
289            }
290    
291            PropertyType[] ftp = new PropertyType[3];
292            ftp[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false );
293            ftp[1] = FeatureFactory.createSimplePropertyType(
294                                                              new QualifiedName( GridCoverageExchange.SHAPE_IMAGE_FILENAME ),
295                                                              Types.VARCHAR, false );
296            ftp[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ),
297                                                              Types.VARCHAR, false );
298            ftype = FeatureFactory.createFeatureType( new QualifiedName( "tiles" ), false, ftp );
299        }
300    
301        /**
302         * @throws IOException
303         */
304        public void logCollectedErrors()
305                                throws IOException {
306            FileOutputStream fos = new FileOutputStream( "RasterTreeBuilder" + minimumRes + ".log" );
307            PrintWriter pw = new PrintWriter( fos );
308            pw.println( "processing the following files caused an error" );
309            Iterator<String> iter = imageFilesErrors.keySet().iterator();
310            while ( iter.hasNext() ) {
311                String key = iter.next();
312                String value = imageFilesErrors.get( key );
313                pw.print( key );
314                pw.print( ": " );
315                pw.println( value );
316            }
317            pw.close();
318            LOG.logInfo( "LOG file RasterTreeBuilder.log has been written" );
319        }
320    
321        /**
322         * starts creating of a raster tile level using the current bbox and resolution
323         * 
324         * @throws Exception
325         */
326        public void start()
327                                throws Exception {
328            System.gc();
329            fc = FeatureFactory.createFeatureCollection( Double.toString( minimumRes ), tileRows * tileCols );
330            createTiles( tileRows, tileCols );
331    
332            LOG.logInfo( "creating shape for georeferencing ... " );
333            ShapeFile sf = new ShapeFile( outputDir + "/sh" + minimumRes, "rw" );
334            sf.writeShape( fc );
335            sf.close();
336    
337        }
338    
339        /**
340         * @param env
341         * @param resolution
342         */
343        public void init( Envelope env, double resolution ) {
344    
345            // set target envelope
346            setEnvelope( env );
347            setResolution( resolution );
348            determineVirtualBBox();
349            determineTileSize();
350        }
351    
352        /**
353         * sets the resolution level to be used for tiling
354         * 
355         * @param resolution
356         */
357        public void setResolution( double resolution ) {
358            minimumRes = resolution;
359        }
360    
361        /**
362         * sets the bounding box used for tiling
363         * 
364         * @param bbox
365         */
366        public void setEnvelope( Envelope bbox ) {
367            combiningEnvelope = bbox;
368        }
369    
370        /**
371         * TODO this is a copy from org.deegree.tools.raster#AutoTiler
372         * 
373         * loads the base image
374         * 
375         * @throws IOException
376         */
377        private TiledImage loadImage( String imageSource )
378                                throws IOException {
379    
380            TiledImage ti = null;
381            Element elem = imgCache.get( imageSource );
382            if ( elem != null ) {
383                ti = (TiledImage) elem.getObjectValue();
384            }
385    
386            if ( ti == null ) {
387                System.out.println( "cache size: " + imgCache.getSize() );
388                System.out.println( "read image: " + imageSource );
389    
390                FileSeekableStream fss = new FileSeekableStream( imageSource );
391                RenderedOp rop = JAI.create( "stream", fss );
392                BufferedImage bi = rop.getAsBufferedImage();
393                try {
394                    fss.close();
395                } catch ( IOException e ) {
396                    // should never happen
397                }
398                ti = new TiledImage( bi, 500, 500 );
399                imgCache.put( new Element( imageSource, ti ) );
400            }
401    
402            return ti;
403        }
404    
405        /**
406         * Determins the necessary size of a bounding box, which is large enough to hold all input image
407         * files. The result is stored in the combining <code>Envelope</code>.
408         * 
409         * @throws Exception
410         */
411        private WorldFile determineCombiningBBox()
412                                throws Exception {
413    
414            System.out.println( "calculating overall bounding box ..." );
415    
416            if ( imageFiles == null || imageFiles.isEmpty() ) {
417                throw new Exception( "No combining BoundingBox to be determined: "
418                                     + "The list of image files is null or empty." );
419            }
420    
421            WorldFile wf1 = null;
422            if ( combiningEnvelope == null ) {
423    
424                // upper left corner of combining bounding box
425                double minX = Double.MAX_VALUE;
426                double maxY = Double.MIN_VALUE;
427                // lower right corner of combining bounding box
428                double maxX = Double.MIN_VALUE;
429                double minY = Double.MAX_VALUE;
430                // minimum resolution within combining bounding box
431                double minResX = Double.MAX_VALUE;
432                double minResY = Double.MAX_VALUE;
433    
434                for ( int i = 0; i < imageFiles.size(); i++ ) {
435    
436                    File file = new File( imageFiles.get( i ) );
437                    if ( file.exists() && !file.isDirectory() ) {
438                        System.out.println( imageFiles.get( i ) );
439                        FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) );
440                        RenderedOp rop = JAI.create( "stream", fss );
441                        int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
442                        int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
443                        fss.close();
444    
445                        WorldFile wf = null;
446    
447                        try {
448                            wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih );
449                        } catch ( Exception e ) {
450                            LOG.logError( e.getMessage() );
451                            continue;
452                        }
453                        imageFilesEnvs.set( i, wf );
454                        // now the values of resx, resy, envelope of the current image
455                        // (read from the world file) file are available
456    
457                        // find min for x and y
458                        minX = Math.min( minX, wf.getEnvelope().getMin().getX() );
459                        minY = Math.min( minY, wf.getEnvelope().getMin().getY() );
460                        // find max for x and y
461                        maxX = Math.max( maxX, wf.getEnvelope().getMax().getX() );
462                        maxY = Math.max( maxY, wf.getEnvelope().getMax().getY() );
463    
464                        // find min for resolution of x and y
465                        minResX = Math.min( minResX, wf.getResx() );
466                        minResY = Math.min( minResY, wf.getResy() );
467                    } else {
468                        System.out.println( "File: " + imageFiles.get( i ) + " does not exist!" );
469                        System.out.println( "Image will be ignored" );
470                    }
471                    if ( i % 10 == 0 ) {
472                        System.gc();
473                    }
474    
475                }
476                // store minimum resolution
477                if ( minimumRes <= 0 ) {
478                    minimumRes = Math.min( minResX, minResY );
479                }
480                combiningEnvelope = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null );
481                LOG.logInfo( "determined envelope: ", combiningEnvelope );
482            }
483            wf1 = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope );
484            return wf1;
485        }
486    
487        /**
488         * Determins a usefull size for the virtual bounding box. It is somewhat larger than the
489         * combining bounding box. The result is stored in the virtual <code>Envelope</code>.
490         * 
491         */
492        private Envelope determineVirtualBBox() {
493    
494            double width = combiningEnvelope.getWidth();
495            double height = combiningEnvelope.getHeight();
496    
497            // set width and height to next higher even-numbered thousand
498            double pxWidth = ( width / minimumRes ) + 1;
499            double pxHeight = ( height / minimumRes ) + 1;
500    
501            pxWidthVirtualBBox = Math.round( pxWidth );
502            pxHeightVirtualBBox = Math.round( pxHeight );
503    
504            // lower right corner of virtual bounding box
505    
506            WorldFile wf = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope );
507            // upper left corner of virtual bounding box
508            double minX = combiningEnvelope.getMin().getX();
509            double maxY = combiningEnvelope.getMax().getY();
510    
511            double maxX = minX + ( ( pxWidth - 1 ) * wf.getResx() );
512            double minY = maxY - ( ( pxHeight - 1 ) * wf.getResx() );
513    
514            return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null );
515    
516            // return combiningEnvelope;
517        }
518    
519        /**
520         * This method determins and sets the size of the tiles in pixel both horizontally (pxWidthTile)
521         * and vertically (pxHeightTile). It also sets the necessary number of <code>tileCols</code>
522         * (depending on the tileWidth) and <code>tileRows</code> (depending on the tileHeight).
523         * 
524         * By default, all tiles have a size of close to but less than 6000 pixel either way.
525         */
526        private void determineTileSize() {
527            /*
528             * The size of the virtual bbox gets divided by maxTileSize to find an approximat number of
529             * tiles (a).
530             * 
531             * If the virtual bbox is in any direction (horizontally or vertically) smaler than
532             * maxTileSize px, then it has only 1 tile in that direction. In this case, the size of the
533             * tile equals the size of the virtual bbox.
534             * 
535             * Otherwise, divide the size of the pixel size of virtual bbox by the pixel tile size
536             * 
537             */
538            // determin width of tile
539            double a = ( pxWidthVirtualBBox / maxTileSize );
540            int tileCols = (int) Math.ceil( a );
541            if ( a <= 1.0 ) {
542                pxWidthTile = pxWidthVirtualBBox;
543            } else {
544                tileCols = (int) ( pxWidthVirtualBBox / ( maxTileSize - 1 ) ) + 1;
545                pxWidthTile = (int) maxTileSize;
546            }
547    
548            // determin height of tile
549            a = ( pxHeightVirtualBBox / maxTileSize );
550            int tileRows = (int) Math.ceil( a );
551            if ( a <= 1.0 ) {
552                pxHeightTile = pxHeightVirtualBBox;
553            } else {
554                tileRows = (int) ( pxHeightVirtualBBox / ( maxTileSize - 1 ) ) + 1;
555                pxHeightTile = (int) maxTileSize;
556            }
557    
558            this.tileCols = tileCols;
559            this.tileRows = tileRows;
560    
561            LOG.logInfo( "minimum resolution: " + minimumRes );
562            LOG.logInfo( "width = " + pxWidthVirtualBBox + " *** height = " + pxHeightVirtualBBox );
563            LOG.logInfo( "pxWidthTile = " + pxWidthTile + " *** pxHeightTile = " + pxHeightTile );
564            LOG.logInfo( "number of tiles: horizontally = " + tileCols + ", vertically = " + tileRows );
565        }
566    
567        /**
568         * Creates one <code>Tile</code> object after the other, with the number of tiles being
569         * specified by the given number of <code>rows</code> and <code>cols</code>.
570         * 
571         * Each Tile gets written to the FileOutputStream by the internal call to #paintImagesOnTile.
572         * 
573         * @param rows
574         * @param cols
575         * @throws IOException
576         * @throws Exception
577         */
578        private void createTiles( int rows, int cols )
579                                throws IOException {
580    
581            System.out.println( "creating merged image ..." );
582    
583            Envelope virtualEnv = determineVirtualBBox();
584    
585            double tileWidth = minimumRes * ( pxWidthTile - 1 );
586            double tileHeight = minimumRes * ( pxHeightTile - 1 );
587    
588            double upperY = virtualEnv.getMax().getY();
589    
590            File file = new File( outputDir + '/' + Double.toString( minimumRes ) ).getAbsoluteFile();
591            file.mkdir();
592    
593            for ( int i = 0; i < rows; i++ ) {
594                System.out.println( "processing row " + i );
595                double leftX = virtualEnv.getMin().getX();
596                double lowerY = upperY - tileHeight;
597                for ( int j = 0; j < cols; j++ ) {
598    
599                    System.out.println( "processing tile: " + i + " - " + j );
600                    double rightX = leftX + tileWidth;
601                    Envelope env = GeometryFactory.createEnvelope( leftX, lowerY, rightX, upperY, null );
602                    leftX = rightX;
603                    String postfix = "_" + i + "_" + j;
604                    Tile tile = new Tile( env, postfix );
605    
606                    paintImagesOnTile( tile );
607                    System.gc();
608                }
609                upperY = lowerY;
610            }
611            System.gc();
612    
613        }
614    
615        /**
616         * Paints all image files that intersect with the passed <code>tile</code> onto that tile and
617         * creates an output file in the <code>outputDir</code>. If no image file intersects with the
618         * given tile, then an empty output file is created. The name of the output file is defined by
619         * the <code>baseName</code> and the tile's index of row and column.
620         * 
621         * @param tile
622         *            The tile on which to paint the image.
623         * @throws IOException
624         */
625        private void paintImagesOnTile( Tile tile )
626                                throws IOException {
627    
628            Envelope tileEnv = tile.getTileEnvelope();
629            String postfix = tile.getPostfix();
630    
631            BufferedImage out = createOutputImage();
632            float[][] data = null;
633            if ( bitDepth == 16 && "raw".equals( outputFormat ) ) {
634                // do not use image api if target bitDepth = 16
635                data = new float[(int) pxHeightTile][(int) pxWidthTile];
636            }
637    
638            if ( bgColor != null ) {
639                Graphics g = out.getGraphics();
640                g.setColor( Color.decode( bgColor ) );
641                g.fillRect( 0, 0, out.getWidth(), out.getHeight() );
642                g.dispose();
643            }
644            boolean paint = false;
645            int gcc = 0;
646    
647            for ( int i = 0; i < imageFiles.size(); i++ ) {
648    
649                File file = new File( imageFiles.get( i ) );
650                if ( imageFilesErrors.get( imageFiles.get( i ) ) == null && file.exists() && !file.isDirectory() ) {
651    
652                    WorldFile wf = imageFilesEnvs.get( i );
653                    if ( wf == null ) {
654                        System.out.println( "read world file" );
655                        // just read image if bbox has not been already read
656                        FileSeekableStream fss = new FileSeekableStream( imageFiles.get( i ) );
657                        RenderedOp rop = JAI.create( "stream", fss );
658                        int iw = ( (Integer) rop.getProperty( "image_width" ) ).intValue();
659                        int ih = ( (Integer) rop.getProperty( "image_height" ) ).intValue();
660                        fss.close();
661                        try {
662                            wf = WorldFile.readWorldFile( imageFiles.get( i ), worldFileType, iw, ih );
663                        } catch ( Exception e ) {
664                            imageFilesErrors.put( imageFiles.get( i ), e.getMessage() );
665                            continue;
666                        }
667                        // cache bounding boxes
668                        imageFilesEnvs.set( i, wf );
669                        gcc++;
670                        if ( gcc % 10 == 0 ) {
671                            System.out.println( "garbage collecting" );
672                            System.gc();
673                        }
674                    }
675                    // now the values of resx, resy, envelope of the current image file are available
676                    if ( wf.getEnvelope().intersects( tileEnv ) ) {
677                        TiledImage bi = loadImage( imageFiles.get( i ) );
678                        gcc++;
679                        try {
680                            System.out.println( "drawImage" );
681                            drawImage( out, data, bi, tile, wf, minimumRes, interpolation, imageFilesErrors, outputFormat,
682                                       bitDepth, offset, scaleFactor );
683                            paint = true;
684                        } catch ( Exception e ) {
685                            e.printStackTrace();
686                            imageFilesErrors.put( imageFiles.get( i ), e.getMessage() );
687                        }
688                        if ( gcc % 5 == 0 ) {
689                            System.out.println( "garbage collecting" );
690                            System.gc();
691                        }
692                    }
693                } else {
694                    imageFilesErrors.put( imageFiles.get( i ), "image does not exist!" );
695                }
696            }
697            if ( paint ) {
698                if ( !isTransparent( out ) ) {
699                    // just write files if something has been painted
700                    if ( bitDepth == 16 && "raw".equals( outputFormat ) ) {
701                        out = RawData2Image.rawData2Image( data, false, scaleFactor, offset );
702                    }
703                    storeTileImageToFileSystem( postfix, out );
704                    createWorldFile( tile );
705                    String frm = outputFormat;
706                    if ( "raw".equals( outputFormat ) ) {
707                        frm = "tif";
708                    }
709                    storeEnvelope( Double.toString( minimumRes ), baseName + postfix + '.' + frm, tileEnv );
710                }
711            }
712        }
713    
714        /**
715         * creates an instance of a BufferedImage depending on requested target format
716         * 
717         * @return the new image
718         */
719        private BufferedImage createOutputImage() {
720    
721            BufferedImage out = null;
722            if ( "jpg".equals( outputFormat ) || "jpeg".equals( outputFormat ) || "bmp".equals( outputFormat ) ) {
723                // for bmp, jpg, jpeg use 3 byte:
724                out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_RGB );
725            } else if ( "tif".equals( outputFormat ) || "tiff".equals( outputFormat ) || "png".equals( outputFormat )
726                        || "gif".equals( outputFormat ) ) {
727                // for tif, tiff and png use 4 byte:
728                out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB );
729            } else {
730                ColorModel ccm;
731    
732                if ( bitDepth == 16 ) {
733                    ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false,
734                                                   BufferedImage.OPAQUE, DataBuffer.TYPE_USHORT );
735                    WritableRaster wr = ccm.createCompatibleWritableRaster( (int) pxWidthTile, (int) pxHeightTile );
736    
737                    out = new BufferedImage( ccm, wr, false, new Hashtable<Object, Object>() );
738                } else {
739                    out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB );
740                }
741            }
742    
743            return out;
744    
745        }
746    
747        /**
748         * 
749         * @param postfix
750         *            tile name postfix ( -> tile index $x_$y )
751         * @param out
752         *            tile image to save
753         */
754        private void storeTileImageToFileSystem( String postfix, BufferedImage out ) {
755            try {
756                String frm = outputFormat;
757                if ( "raw".equals( frm ) ) {
758                    frm = "tif";
759                }
760                String imageFile = outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + '.' + frm;
761                File file = new File( imageFile ).getAbsoluteFile();
762    
763                ImageUtils.saveImage( out, file, quality );
764    
765            } catch ( IOException e ) {
766                e.printStackTrace();
767            }
768        }
769    
770        /**
771         * Draws an image map to the target tile considering defined interpolation method for rescaling.
772         * This method is static so it can be used easily from the <code>RasterTreeUpdater</code>.
773         * 
774         * @param out
775         *            target image tile
776         * @param data
777         * @param image
778         *            source image map
779         * @param tile
780         *            tile description, must contain the envelope of the target image
781         * @param wf
782         *            must contain the envelope of the TiledImage of the source image
783         * @param minimumRes
784         *            the minimum resolution of input images
785         * @param interpolation
786         *            the interpolation method
787         * @param imageFilesErrors
788         *            a mapping between image files and errors
789         * @param outputFormat
790         *            the output format
791         * @param bitDepth
792         *            the output bit depth
793         * @param offset
794         *            offset used if bitDepth = 16 and outputFormat = raw
795         * @param scaleFactor
796         *            scale factor used if bitDepth = 16 and outputFormat = raw
797         */
798        public static void drawImage( BufferedImage out, float[][] data, final TiledImage image, Tile tile, WorldFile wf,
799                                      double minimumRes, Interpolation interpolation, Map<String, String> imageFilesErrors,
800                                      String outputFormat, int bitDepth, float offset, float scaleFactor ) {
801    
802            Envelope tileEnv = tile.getTileEnvelope();
803            Envelope mapEnv = wf.getEnvelope();
804    
805            GeoTransform gt2 = new WorldToScreenTransform( mapEnv.getMin().getX(), mapEnv.getMin().getY(),
806                                                           mapEnv.getMax().getX(), mapEnv.getMax().getY(), 0, 0,
807                                                           image.getWidth() - 1, image.getHeight() - 1 );
808    
809            Envelope inter = mapEnv.createIntersection( tileEnv );
810            if ( inter == null )
811                return;
812            int x1 = (int) Math.round( gt2.getDestX( inter.getMin().getX() ) );
813            int y1 = (int) Math.round( gt2.getDestY( inter.getMax().getY() ) );
814            int x2 = (int) Math.round( gt2.getDestX( inter.getMax().getX() ) );
815            int y2 = (int) Math.round( gt2.getDestY( inter.getMin().getY() ) );
816    
817            if ( x2 - x1 >= 0 && y2 - y1 >= 0 && x1 + x2 - x1 < image.getWidth() && y1 + y2 - y1 < image.getHeight()
818                 && x1 >= 0 && y1 >= 0 ) {
819    
820                BufferedImage newImg = null;
821                int w = x2 - x1 + 1;
822                int h = y2 - y1 + 1;
823                // System.out.println( x1 + " " + y1 + " " + w + " " + h + " " + image.getWidth() + " "
824                // + image.getHeight() );
825                BufferedImage img = image.getSubImage( x1, y1, w, h ).getAsBufferedImage();
826    
827                if ( !isTransparent( img ) ) {
828    
829                    // copy source image to a 4 Byte BufferedImage because there are
830                    // problems with handling 8 Bit palette images
831                    if ( img.getColorModel().getPixelSize() == 18 ) {
832                        LOG.logInfo( "copy 8Bit image to 32Bit image" );
833                        BufferedImage bi = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB );
834    
835                        Graphics g = bi.getGraphics();
836                        try {
837                            g.drawImage( img, 0, 0, null );
838                        } catch ( Exception e ) {
839                            System.out.println( e.getMessage() );
840                        }
841                        g.dispose();
842                        img = bi;
843                    }
844                    if ( ( wf.getResx() / minimumRes < 0.9999 ) || ( wf.getResx() / minimumRes > 1.0001 )
845                         || ( wf.getResy() / minimumRes < 0.9999 ) || ( wf.getResy() / minimumRes > 1.0001 ) ) {
846                        newImg = scale( img, interpolation, (float) ( wf.getResx() / minimumRes ),
847                                        (float) ( wf.getResy() / minimumRes ) );
848                    } else {
849                        newImg = img;
850                    }
851                    GeoTransform gt = new WorldToScreenTransform( tileEnv.getMin().getX(), tileEnv.getMin().getY(),
852                                                                  tileEnv.getMax().getX(), tileEnv.getMax().getY(), 0, 0,
853                                                                  out.getWidth() - 1, out.getHeight() - 1 );
854    
855                    x1 = (int) Math.round( gt.getDestX( inter.getMin().getX() ) );
856                    y1 = (int) Math.round( gt.getDestY( inter.getMax().getY() ) );
857                    x2 = (int) Math.round( gt.getDestX( inter.getMax().getX() ) );
858                    y2 = (int) Math.round( gt.getDestY( inter.getMin().getY() ) );
859    
860                    if ( x2 - x1 > 0 && y2 - y1 > 0 ) {
861                        // ensure that there is something to draw
862                        try {
863                            if ( "raw".equals( outputFormat ) ) {
864                                DataBuffer outBuffer = out.getData().getDataBuffer();
865                                DataBuffer newImgBuffer = newImg.getData().getDataBuffer();
866                                int ps = newImg.getColorModel().getPixelSize();
867                                float[][] newData = null;
868                                if ( bitDepth == 16 && ps == 16 ) {
869                                    Image2RawData i2r = new Image2RawData( newImg, 1f / scaleFactor, -1 * offset );
870                                    // do not use image api if target bitDepth = 16
871                                    newData = i2r.parse();
872                                }
873                                for ( int i = 0; i < newImg.getWidth(); i++ ) {
874                                    for ( int j = 0; j < newImg.getHeight(); j++ ) {
875                                        if ( x1 + i < out.getWidth() && y1 + j < out.getHeight() ) {
876                                            int newImgPos = newImg.getWidth() * j + i;
877                                            int outPos = out.getWidth() * ( y1 + j ) + ( x1 + i );
878                                            if ( bitDepth == 16 && ps == 16 ) {
879                                                // int v = newImgBuffer.getElem( newImgPos );
880                                                // outBuffer.setElem( outPos, v );
881                                                data[y1 + j][x1 + i] = newData[j][i];
882                                            } else if ( bitDepth == 16 && ps == 32 ) {
883                                                int v = newImg.getRGB( i, j );
884                                                float f = Float.intBitsToFloat( v ) * 10f;
885                                                outBuffer.setElem( outPos, Math.round( f ) );
886                                                // TODO
887                                                // data[y1 + j][x1 + i] = f;
888                                            } else if ( bitDepth == 32 && ps == 16 ) {
889                                                float f = newImgBuffer.getElem( newImgPos ) / 10f;
890                                                outBuffer.setElem( outPos, Float.floatToIntBits( f ) );
891                                            } else {
892                                                out.setRGB( x1 + i, y1 + j, newImg.getRGB( i, j ) );
893                                            }
894                                        }
895                                    }
896                                }
897                                if ( ( bitDepth == 16 && ps == 16 ) || ( bitDepth == 16 && ps == 32 )
898                                     || ( bitDepth == 32 && ps == 16 ) ) {
899                                    out.setData( Raster.createRaster( out.getSampleModel(), outBuffer, null ) );
900                                }
901                            } else {
902                                Graphics g = out.getGraphics();
903                                g.drawImage( newImg, x1, y1, newImg.getWidth(), newImg.getHeight(), null );
904                                g.dispose();
905                            }
906                        } catch ( Exception e ) {
907                            LOG.logError( "Could not draw upon the image: " );
908                            LOG.logError( "New image is of size " + newImg.getWidth() + ", " + newImg.getHeight() );
909                            LOG.logError( "Position/width tried is (" + x1 + ", " + y1 + ", " + newImg.getWidth() + ", "
910                                          + newImg.getHeight() + ")" );
911                            if ( imageFilesErrors != null ) {
912                                imageFilesErrors.put( tile.getPostfix(), StringTools.stackTraceToString( e ) );
913                            }
914                        }
915                    }
916                }
917            }
918        }
919    
920        /**
921         * 
922         * @param img
923         * @param interpolation
924         * @param scaleX
925         * @param scaleY
926         * @return the scaled image
927         */
928        private static BufferedImage scale( BufferedImage img, Interpolation interpolation, float scaleX, float scaleY ) {
929    
930            LOG.logDebug( "Scale image: by factors: " + scaleX + ' ' + scaleY );
931            ParameterBlock pb = new ParameterBlock();
932            pb.addSource( img );
933            pb.add( scaleX ); // The xScale
934            pb.add( scaleY ); // The yScale
935            pb.add( 0.0F ); // The x translation
936            pb.add( 0.0F ); // The y translation
937            pb.add( interpolation ); // The interpolation
938            // Create the scale operation
939            RenderedOp ro = JAI.create( "scale", pb, null );
940            try {
941                img = ro.getAsBufferedImage();
942            } catch ( Exception e ) {
943                e.printStackTrace();
944            }
945            return img;
946        }
947    
948        private static boolean isTransparent( BufferedImage bi ) {
949            /*
950             * TODO determine if the passed image is completly transparent for ( int i = 0; i <
951             * bi.getHeight(); i++ ) { for ( int j = 0; j < bi.getWidth(); j++ ) { if ( bi.getRGB( i, j ) !=
952             * 0 && bi.getRGB( i, j ) != -256 ) { return false; } } } return true;
953             */
954            return false;
955        }
956    
957        /**
958         * @return an interpolation object from a well known name
959         * @param interpolation
960         */
961        public static Interpolation createInterpolation( String interpolation ) {
962            Interpolation interpol = null;
963    
964            if ( interpolation.equalsIgnoreCase( "Nearest Neighbor" ) ) {
965                interpol = new InterpolationNearest();
966            } else if ( interpolation.equalsIgnoreCase( "Bicubic" ) ) {
967                interpol = new InterpolationBicubic( 5 );
968            } else if ( interpolation.equalsIgnoreCase( "Bicubic2" ) ) {
969                interpol = new InterpolationBicubic2( 5 );
970            } else if ( interpolation.equalsIgnoreCase( "Bilinear" ) ) {
971                interpol = new InterpolationBilinear();
972            } else {
973                throw new RuntimeException( "invalid interpolation method: " + interpolation );
974            }
975    
976            return interpol;
977        }
978    
979        /**
980         * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of
981         * the output file is defined by the <code>baseName</code> and the tile's index of row and
982         * column.
983         * 
984         * @param tile
985         *            The tile for which to create a world file.
986         * @throws IOException
987         */
988        private void createWorldFile( Tile tile )
989                                throws IOException {
990    
991            Envelope env = tile.getTileEnvelope();
992            String postfix = tile.getPostfix();
993    
994            StringBuffer sb = new StringBuffer( 1000 );
995    
996            sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 );
997            sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" );
998            sb.append( env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() );
999            sb.append( "\n" );
1000    
1001            File f = new File( outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + ".wld" );
1002    
1003            FileWriter fw = new FileWriter( f );
1004            PrintWriter pw = new PrintWriter( fw );
1005    
1006            pw.print( sb.toString() );
1007    
1008            pw.close();
1009            fw.close();
1010        }
1011    
1012        /**
1013         * stores an envelope and the assigend image file information into a feature/featureCollection
1014         * 
1015         * @param dir
1016         *            directory where the image file is stored
1017         * @param file
1018         *            name of the image file
1019         * @param env
1020         *            bbox of the image file
1021         */
1022        private void storeEnvelope( String dir, String file, Envelope env ) {
1023            try {
1024                Geometry geom = GeometryFactory.createSurface( env, null );
1025                FeatureProperty[] props = new FeatureProperty[3];
1026                props[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), geom );
1027                props[1] = FeatureFactory.createFeatureProperty(
1028                                                                 new QualifiedName(
1029                                                                                    GridCoverageExchange.SHAPE_IMAGE_FILENAME ),
1030                                                                 file );
1031                props[2] = FeatureFactory.createFeatureProperty( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ),
1032                                                                 dir );
1033                Feature feat = FeatureFactory.createFeature( "file", ftype, props );
1034                fc.add( feat );
1035            } catch ( Exception e ) {
1036                e.printStackTrace();
1037            }
1038        }
1039    
1040        /**
1041         * creates a configuration file (extended CoverageDescriotion) for a WCS coverage considering
1042         * the passed resolution levels
1043         * 
1044         * @param targetResolutions
1045         */
1046        private void createConfigurationFile( double[] targetResolutions ) {
1047    
1048            // copy this file to the target directory
1049            String resolutions = "";
1050            sort( targetResolutions );
1051            int length = targetResolutions.length;
1052    
1053            for ( int i = 0; i < length; i++ ) {
1054                resolutions += String.valueOf( targetResolutions[length - 1 - i] );
1055                if ( i < ( length - 1 ) )
1056                    resolutions += ',';
1057            }
1058    
1059            try {
1060                Map<String, String> param = new HashMap<String, String>( 20 );
1061                Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1062                param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1063                                          + String.valueOf( llEnv.getMin().getY() ) );
1064                param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1065                                           + String.valueOf( llEnv.getMax().getY() ) );
1066                param.put( "upperleft", String.valueOf( combiningEnvelope.getMin().getX() ) + ','
1067                                        + String.valueOf( combiningEnvelope.getMin().getY() ) );
1068                param.put( "lowerright", String.valueOf( combiningEnvelope.getMax().getX() ) + ','
1069                                         + combiningEnvelope.getMax().getY() );
1070                File dir = new File( outputDir );
1071                if ( dir.isAbsolute() ) {
1072                    // param.put( "dataDir", outputDir + '/' );
1073                    param.put( "dataDir", "" );
1074                } else {
1075                    param.put( "dataDir", "" );
1076                }
1077                param.put( "label", baseName );
1078                param.put( "name", baseName );
1079                param.put( "description", "" );
1080                param.put( "keywords", "" );
1081                param.put( "resolutions", resolutions );
1082                String frm = outputFormat;
1083                if ( "raw".equals( outputFormat ) && bitDepth == 32 ) {
1084                    frm = "tif";
1085                } else if ( "raw".equals( outputFormat ) && bitDepth == 16 ) {
1086                    frm = "GeoTiff";
1087                }
1088                param.put( "mimeType", frm );
1089                int p = srs.lastIndexOf( ':' );
1090                param.put( "srs", srs.substring( p + 1, srs.length() ) );
1091                param.put( "srsPre", srs.substring( 0, p + 1 ) );
1092    
1093                Reader reader = new InputStreamReader( configURL.openStream() );
1094    
1095                XSLTDocument xslt = new XSLTDocument();
1096                xslt.load( configXSL );
1097                XMLFragment xml = xslt.transform( reader, XMLFragment.DEFAULT_URL, null, param );
1098                reader.close();
1099    
1100                // write the result
1101                String dstFilename = "wcs_" + baseName + "_configuration.xml";
1102                File dstFile = new File( outputDir, dstFilename );
1103                String configurationFilename = dstFile.getAbsolutePath().toString();
1104                FileOutputStream fos = new FileOutputStream( configurationFilename );
1105                xml.write( fos );
1106                fos.close();
1107    
1108            } catch ( Exception e1 ) {
1109                e1.printStackTrace();
1110            }
1111    
1112        }
1113    
1114        private Envelope getLatLonEnvelope( Envelope env )
1115                                throws Exception {
1116            GeoTransformer gt = new GeoTransformer( "EPSG:4326" );
1117            return gt.transform( env, srs );
1118        }
1119    
1120        /**
1121         * 
1122         */
1123        private void updateCapabilitiesFile( File capabilitiesFile ) {
1124    
1125            try {
1126                XSLTDocument xslt = new XSLTDocument();
1127                xslt.load( inputXSL );
1128                Map<String, String> param = new HashMap<String, String>();
1129    
1130                param.put( "dataDirectory", outputDir );
1131                String url = new File( "wcs_" + baseName + "_configuration.xml" ).toURL().toString();
1132                param.put( "configFile", url );
1133                Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1134                param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1135                                          + String.valueOf( llEnv.getMin().getY() ) );
1136                param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1137                                           + String.valueOf( llEnv.getMax().getY() ) );
1138    
1139                param.put( "name", baseName );
1140                param.put( "label", baseName );
1141    
1142                param.put( "description", "" );
1143                param.put( "keywords", "" );
1144    
1145                XMLFragment xml = new XMLFragment();
1146                xml.load( capabilitiesFile.toURL() );
1147    
1148                xml = xslt.transform( xml, capabilitiesFile.toURL().toExternalForm(), null, param );
1149    
1150                // write the result
1151                FileOutputStream fos = new FileOutputStream( capabilitiesFile );
1152                xml.write( fos );
1153                fos.close();
1154            } catch ( Exception e ) {
1155                e.printStackTrace();
1156            }
1157        }
1158    
1159        /**
1160         * Validates the content of <code>map</code>, to see, if necessary arguments were passed when
1161         * calling this class.
1162         * 
1163         * @param map
1164         * @throws Exception
1165         */
1166        private static void validate( Properties map )
1167                                throws Exception {
1168    
1169            if ( map.get( "-outDir" ) == null ) {
1170                throw new Exception( "-outDir must be set" );
1171            }
1172            String s = (String) map.get( "-outDir" );
1173            if ( s.endsWith( "/" ) || s.endsWith( "\\" ) ) {
1174                s = s.substring( 0, s.length() - 1 );
1175            }
1176    
1177            if ( map.get( "-baseName" ) == null ) {
1178                throw new Exception( "-baseName must be set" );
1179            }
1180            if ( map.get( "-outputFormat" ) == null ) {
1181                map.put( "-outputFormat", "png" );
1182            } else {
1183                String format = ( (String) map.get( "-outputFormat" ) ).toLowerCase();
1184                if ( !"bmp".equals( format ) && !"png".equals( format ) && !"jpg".equals( format )
1185                     && !"jpeg".equals( format ) && !"tif".equals( format ) && !"tiff".equals( format )
1186                     && !"gif".equals( format ) && !( "raw" ).equals( format ) ) {
1187    
1188                    throw new Exception( "-outputFormat must be one of the following: "
1189                                         + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'." );
1190                }
1191            }
1192            if ( map.get( "-maxTileSize" ) == null ) {
1193                map.put( "-maxTileSize", "500" );
1194            }
1195            if ( map.get( "-srs" ) == null ) {
1196                map.put( "-srs", "EPSG:4326" );
1197            }
1198            if ( map.get( "-interpolation" ) == null ) {
1199                map.put( "-interpolation", "Nearest Neighbor" );
1200            }
1201            if ( map.get( "-noOfLevel" ) == null ) {
1202                map.put( "-noOfLevel", "1" );
1203            }
1204            if ( map.get( "-worldFileType" ) == null ) {
1205                map.put( "-worldFileType", "center" );
1206            }
1207            if ( map.get( "-quality" ) == null ) {
1208                map.put( "-quality", "0.95" );
1209            }
1210            if ( map.get( "-bbox" ) != null ) {
1211                double[] d = StringTools.toArrayDouble( (String) map.get( "-bbox" ), "," );
1212                Envelope env = GeometryFactory.createEnvelope( d[0], d[1], d[2], d[3], null );
1213                map.put( "-bbox", env );
1214                if ( map.get( "-resolution" ) == null ) {
1215                    throw new Exception( "-resolution must be set if -bbox is set" );
1216                }
1217                map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1218            } else {
1219                if ( map.get( "-resolution" ) == null ) {
1220                    map.put( "-resolution", new Double( -1 ) );
1221                } else {
1222                    map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1223                }
1224            }
1225        }
1226    
1227        /**
1228         * @return the list of image map files to consider read from -mapFiles parameter
1229         * 
1230         * @param mapFiles
1231         */
1232        private static List<String> getFileList( String[] mapFiles ) {
1233            List<String> imageFiles = new ArrayList<String>();
1234            for ( int i = 0; i < mapFiles.length; i++ ) {
1235                imageFiles.add( mapFiles[i] );
1236            }
1237            return imageFiles;
1238        }
1239    
1240        /**
1241         * @return the list of image map files to consider read from a defined root directory.
1242         * 
1243         * @param rootDir
1244         *            root directory where to read image map files
1245         * @param subdirs
1246         *            true if subdirectories of the root directory shall be parsed for image maps too
1247         */
1248        private static List<String> getFileList( String rootDir, boolean subdirs ) {
1249            List<String> list = new ArrayList<String>( 10000 );
1250            File file = new File( rootDir );
1251            String[] entries = file.list( new DFileFilter() );
1252            if ( entries != null ) {
1253                for ( int i = 0; i < entries.length; i++ ) {
1254                    File entry = new File( rootDir + '/' + entries[i] );
1255                    if ( entry.isDirectory() && subdirs ) {
1256                        list = readSubDirs( entry, list );
1257                    } else {
1258                        list.add( rootDir + '/' + entries[i] );
1259                    }
1260                }
1261            }
1262            return list;
1263        }
1264    
1265        /**
1266         * 
1267         * @param file
1268         * @param list
1269         * @return the sub directories
1270         */
1271        private static List<String> readSubDirs( File file, List<String> list ) {
1272    
1273            String[] entries = file.list( new DFileFilter() );
1274            if ( entries != null ) {
1275                for ( int i = 0; i < entries.length; i++ ) {
1276                    File entry = new File( file.getAbsolutePath() + '/' + entries[i] );
1277                    if ( entry.isDirectory() ) {
1278                        list = readSubDirs( entry, list );
1279                    } else {
1280                        list.add( file.getAbsolutePath() + '/' + entries[i] );
1281                    }
1282                }
1283            }
1284            return list;
1285        }
1286    
1287        /**
1288         * @return the list of image map files to consider read from a dbase file defined by the dbase
1289         *         parameter
1290         * 
1291         * @param dbaseFile
1292         *            name of the dbase file
1293         * @param fileColumn
1294         *            name of the column containing the image map files names
1295         * @param baseDir
1296         *            name of the directory where the image map files are stored if this parameter is
1297         *            <code>null</code> it is assumed that the image map files are full referenced
1298         *            within the dbase
1299         * @param sort
1300         *            true if map image file names shall be sorted
1301         * @param sortColum
1302         *            name of the column that shall be used for sorting
1303         */
1304        private static List<String> getFileList( String dBaseFile, String fileColumn, String baseDir, boolean sort,
1305                                                 String sortColum, String sortDirection )
1306                                throws Exception {
1307    
1308            // handle dbase file extension and file location/reading problems
1309            if ( dBaseFile.endsWith( ".dbf" ) ) {
1310                dBaseFile = dBaseFile.substring( 0, dBaseFile.lastIndexOf( "." ) );
1311            }
1312            DBaseFile dbf = new DBaseFile( dBaseFile );
1313    
1314            // sort dbase file contents chronologicaly (oldest first)
1315            int cnt = dbf.getRecordNum();
1316    
1317            Object[][] mapItems = new Object[cnt][2];
1318            QualifiedName fileC = new QualifiedName( APP_PREFIX, fileColumn.toUpperCase(), DEEGREEAPP );
1319            QualifiedName sortC = null;
1320            if ( sort ) {
1321                sortC = new QualifiedName( APP_PREFIX, sortColum.toUpperCase(), DEEGREEAPP );
1322            }
1323            for ( int i = 0; i < cnt; i++ ) {
1324                if ( sort ) {
1325                    mapItems[i][0] = dbf.getFRow( i + 1 ).getDefaultProperty( sortC ).getValue();
1326                } else {
1327                    mapItems[i][0] = new Integer( 1 );
1328                }
1329                // name of map file
1330                mapItems[i][1] = dbf.getFRow( i + 1 ).getDefaultProperty( fileC ).getValue();
1331            }
1332            Arrays.sort( mapItems, new MapAgeComparator( sortDirection ) );
1333    
1334            // extract names of image files from dBase file and attach them to rootDir
1335            if ( baseDir == null ) {
1336                baseDir = "";
1337            } else if ( !baseDir.endsWith( "/" ) && !baseDir.endsWith( "\\" ) ) {
1338                baseDir = baseDir + "/";
1339            }
1340            List<String> imageFiles = new ArrayList<String>( mapItems.length );
1341            for ( int i = 0; i < mapItems.length; i++ ) {
1342                if ( mapItems[i][0] != null ) {
1343                    LOG.logDebug( "" + mapItems[i][0] );
1344                    imageFiles.add( baseDir + mapItems[i][1] );
1345                }
1346            }
1347    
1348            return imageFiles;
1349        }
1350    
1351        private static void printHelp() {
1352    
1353            System.out.println( "-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n"
1354                                + "-redirect whether to redirect the standard output/error streams to a file rtb.log in the output directory. Default is false.\r\n"
1355                                + "-baseName base name used for creating names of the raster tile files. It also will be the name of the created coverage. (mandatory)\r\n"
1356                                + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n"
1357                                + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n"
1358                                + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n"
1359                                + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n"
1360                                + "               be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n"
1361                                + "               If you use the proogram with images (tif) containing raw data like DEMs just use \r\n"
1362                                + "               Nearest Neighbor interpolation. All other interpolation methods will cause artefacts."
1363                                + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n"
1364                                + "-resolution spatial resolution of the resulting coverage. If not set the resolution will determined by analysing the input map files. This parameter is conditional; if -bbox is defined -resolution must be defined too.\r\n"
1365                                + "-noOfLevel number of tree levels created (optional default = 1)\r\n"
1366                                + "-capabilitiesFile name of a deegree WCS capabilities/configuration file. If defined the program will add the created rastertree as a new coverage to the WCS configuration.\r\n"
1367                                + "-h or -? print this help\r\n"
1368                                + "\r\n"
1369                                + "Input files\r\n"
1370                                + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n"
1371                                + "1)\r\n"
1372                                + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n"
1373                                + "\r\n"
1374                                + "2)\r\n"
1375                                + "-rootDir defines a directory that shall be parsed for files in a known image format. Each file found will be used as input.\r\n"
1376                                + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n"
1377                                + "\r\n"
1378                                + "3)\r\n"
1379                                + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n"
1380                                + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n"
1381                                + "-baseDir name of the directory where the files are stored. If this parameter will not be set the program assumes the -fileColumn contains completely referenced file names (optional)\r\n"
1382                                + "-sortColumn If -dbaseFile is defined one can define a column that shall be used for sorting the files referenced by the -fileColumn (optional)\r\n"
1383                                + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n"
1384                                + "-worldFileType two types of are common: \r\n "
1385                                + "               a) the boundingbox is defined on the center of the corner pixels; \r\n "
1386                                + "               b) the boundingbox is defined on the outer corner of the corner pixels; \r\n "
1387                                + "               first is default and will be used if this parameter is not set; second will be use if '-worldFileType outer' is defined.\r\n"
1388                                + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n"
1389                                + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n"
1390                                + "-bgColor defines the background color of the created tiles for those region no data are available (e.g. -bgColor 0xFFFFF defines background as white) \r\n"
1391                                + "         If no -bgColor is defined, transparent background will be used for image formats that are transparency enabled (e.g. png) and black is used for all other formats (e.g. bmp) \r\n"
1392                                + "-offset defines the offset added to raster values if -outputFormat = raw and -bitDepth (default 0) \r\n"
1393                                + "-scaleFactor defines the factor by which raster values are multiplied if -outputFormat = raw and -bitDepth (default 1) \r\n"
1394                                + "\r\n"
1395                                + "Common to all option defining the input files is that each referenced file must be in a known image format (png, tif, jpeg, bmp, gif) and if must be geo-referenced by a world file or must be a GeoTIFF." );
1396            System.out.println();
1397            System.out.println( "caching:" );
1398            System.out.println( "To use default caching mechanism you just have to start RTB as before; to define " );
1399            System.out.println( "your own caching behavior you have to place a file named ehcache.xml within the " );
1400            System.out.println( "root of your classpath. The content of this file is described by the ehcache documentation " );
1401            System.out.println( "(http://ehcache.sourceforge.net/documentation); at least it must provide a cache named 'imgCache'." );
1402            System.out.println( " When defining your own cache please consider that just 'inMemory' caching is supported because " );
1403            System.out.println( "the objects cached by RTB are not serializable." );
1404            System.out.println();
1405            System.out.println( "Example invoking RTB (windows):" );
1406            System.out.println( "java -Xms300m -Xmx1000m -classpath .;.\\lib\\deegree2.jar;.\\lib\\acme.jar;"
1407                                + ".\\lib\\batik-awt-util.jar;.\\lib\\commons-beanutils-1.5.jar;"
1408                                + ".\\lib\\commons-codec-1.3.jar;.\\lib\\commons-collections-3.1.jar;"
1409                                + ".\\lib\\commons-digester-1.7.jar;.\\lib\\commons-discovery-0.2.jar;"
1410                                + ".\\lib\\commons-logging.jar;.\\lib\\jai_codec.jar;.\\lib\\jai_core.jar;"
1411                                + ".\\lib\\mlibwrapper_jai.jar;.\\lib\\j3dcore.jar;.\\lib\\j3dutils.jar;"
1412                                + ".\\lib\\vecmath.jar;.\\lib\\jts-1.6.jar;.\\lib\\log4j-1.2.9.jar;"
1413                                + ".\\lib\\axis.jar;.\\lib\\jaxen-1.1-beta-7.jar;.\\lib\\ehcache-1.2.0_03.jar "
1414                                + "org.deegree.tools.raster.RasterTreeBuilder "
1415                                + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ "
1416                                + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation "
1417                                + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn "
1418                                + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir "
1419                                + "D:/lgv/resources/data/images/ " );
1420        }
1421    
1422        /**
1423         * 
1424         * @param args
1425         *            Example arguments to pass when calling are:
1426         *            <ul>
1427         *            <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li>
1428         *            <li>-outDir D:/temp/out/</li>
1429         *            <li>-baseName pretty</li>
1430         *            <li>-outputFormat png</li>
1431         *            <li>-maxTileSize 600</li>
1432         *            </ul>
1433         * 
1434         * @throws Exception
1435         */
1436        public static void main( String[] args )
1437                                throws Exception {
1438    
1439            Properties map = new Properties();
1440            for ( int i = 0; i < args.length; i += 2 ) {
1441                map.put( args[i], args[i + 1] );
1442            }
1443    
1444            if ( map.get( "-?" ) != null || map.get( "-h" ) != null ) {
1445                printHelp();
1446                return;
1447            }
1448    
1449            try {
1450                validate( map );
1451            } catch ( Exception e ) {
1452                LOG.logInfo( map.toString() );
1453                System.out.println( e.getMessage() );
1454                System.out.println();
1455                printHelp();
1456                return;
1457            }
1458    
1459            String outDir = map.getProperty( "-outDir" );
1460    
1461            // set up stderr/stdout redirection
1462            String redirect = map.getProperty( "-redirect" );
1463            if ( redirect != null && redirect.equals( "true" ) ) {
1464                File f = new File( outDir + separator + "rtb.log" );
1465                PrintStream out = new PrintStream( new FileOutputStream( f ) );
1466                System.setOut( out );
1467                System.setErr( out );
1468            }
1469    
1470            // read input parameters
1471            String baseName = map.getProperty( "-baseName" );
1472            String outputFormat = map.getProperty( "-outputFormat" );
1473            String srs = map.getProperty( "-srs" );
1474            if ( srs == null ) {
1475                srs = "EPSG:4326";
1476            }
1477            String interpolation = map.getProperty( "-interpolation" );
1478            Envelope env = (Envelope) map.get( "-bbox" );
1479            double resolution = ( (Double) map.get( "-resolution" ) ).doubleValue();
1480            int level = Integer.parseInt( map.getProperty( "-noOfLevel" ) );
1481            double maxTileSize = ( Double.valueOf( map.getProperty( "-maxTileSize" ) ) ).doubleValue();
1482            WorldFile.TYPE worldFileType = WorldFile.TYPE.CENTER;
1483            if ( "outer".equals( map.getProperty( "-worldFileType" ) ) ) {
1484                worldFileType = WorldFile.TYPE.OUTER;
1485            }
1486            float quality = Float.parseFloat( map.getProperty( "-quality" ) );
1487            String backgroundColor = map.getProperty( "-bgColor" );
1488    
1489            int depth = 0;
1490    
1491            if ( map.get( "-bitDepth" ) != null ) {
1492                depth = Integer.parseInt( map.getProperty( "-bitDepth" ) );
1493            }
1494    
1495            float offset = 0;
1496            if ( map.get( "-offset" ) != null ) {
1497                offset = Float.parseFloat( map.getProperty( "-offset" ) );
1498            }
1499    
1500            float scaleFactor = 1;
1501            if ( map.get( "-scaleFactor" ) != null ) {
1502                scaleFactor = Float.parseFloat( map.getProperty( "-scaleFactor" ) );
1503            }
1504    
1505            List<String> imageFiles = null;
1506            if ( map.get( "-mapFiles" ) != null ) {
1507                String[] mapFiles = StringTools.toArray( map.getProperty( "-mapFiles" ), ",;|", true );
1508                imageFiles = getFileList( mapFiles );
1509            } else if ( map.get( "-dbaseFile" ) != null ) {
1510                String dBaseFile = map.getProperty( "-dbaseFile" );
1511                String fileColum = map.getProperty( "-fileColumn" );
1512                String baseDir = map.getProperty( "-baseDir" );
1513                if ( baseDir == null ) {
1514                    baseDir = map.getProperty( "-rootDir" );
1515                }
1516                boolean sort = map.get( "-sortColumn" ) != null;
1517                String sortColumn = map.getProperty( "-sortColumn" );
1518                if ( map.get( "-sortDirection" ) == null ) {
1519                    map.put( "-sortDirection", "UP" );
1520                }
1521                String sortDirection = map.getProperty( "-sortDirection" );
1522                imageFiles = getFileList( dBaseFile, fileColum, baseDir, sort, sortColumn, sortDirection );
1523            } else if ( map.get( "-rootDir" ) != null ) {
1524                String rootDir = map.getProperty( "-rootDir" );
1525                boolean subDirs = "true".equals( map.get( "-subDirs" ) );
1526                imageFiles = getFileList( rootDir, subDirs );
1527            } else {
1528                LOG.logInfo( map.toString() );
1529                System.out.println( "-mapFiles, -rootDir or -dbaseFile parameter must be defined" );
1530                printHelp();
1531                return;
1532            }
1533    
1534            LOG.logDebug( imageFiles.toString() );
1535            LOG.logInfo( map.toString() );
1536    
1537            // initialize RasterTreeBuilder
1538            RasterTreeBuilder rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1539                                                           interpolation, worldFileType, quality, backgroundColor, depth,
1540                                                           resolution, offset, scaleFactor );
1541    
1542            // calculate bbox and resolution from input images if parameters are not set
1543            if ( env == null ) {
1544                WorldFile wf = rtb.determineCombiningBBox();
1545                env = wf.getEnvelope();
1546                resolution = wf.getResx();
1547            }
1548    
1549            // Calculate necessary number of levels to get not more than 4
1550            // tiles in highest resolution
1551            if ( level == -1 ) {
1552                rtb.init( env, resolution );
1553                level = 0;
1554                int numTilesMax = Math.min( rtb.tileCols, rtb.tileRows );
1555                int numTiles = 4;
1556                while ( numTiles < numTilesMax ) {
1557                    level += 1;
1558                    numTiles *= 2;
1559                }
1560            }
1561            if ( level == 0 ) {
1562                level = 1;
1563            }
1564            System.out.println( "Number of Levels: " + level );
1565    
1566            // create tree where for each loop resolution will be halfed
1567            double[] re = new double[level];
1568            for ( int i = 0; i < level; i++ ) {
1569                rtb.init( env, resolution );
1570                rtb.start();
1571                rtb.logCollectedErrors();
1572                re[i] = resolution;
1573                if ( i < level - 1 ) {
1574                    String dir = outDir + '/' + Double.toString( resolution );
1575                    imageFiles = getFileList( dir, false );
1576                    rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1577                                                 interpolation, WorldFile.TYPE.CENTER, quality, backgroundColor, depth,
1578                                                 resolution, offset, scaleFactor );
1579                }
1580                resolution = resolution * 2;
1581            }
1582    
1583            LOG.logInfo( "create configuration files ..." );
1584            rtb.createConfigurationFile( re );
1585    
1586            if ( map.get( "-capabilitiesFile" ) != null ) {
1587                LOG.logInfo( "adjust capabilities ..." );
1588                File file = new File( map.getProperty( "-capabilitiesFile" ) );
1589                rtb.updateCapabilitiesFile( file );
1590            }
1591    
1592            rtb.logCollectedErrors();
1593        }
1594    
1595        /**
1596         * class: official version of a FilenameFilter
1597         */
1598        static class DFileFilter implements FilenameFilter {
1599    
1600            private List<String> extensions = null;
1601    
1602            /**
1603             * 
1604             */
1605            public DFileFilter() {
1606                extensions = new ArrayList<String>();
1607                extensions.add( "JPEG" );
1608                extensions.add( "JPG" );
1609                extensions.add( "BMP" );
1610                extensions.add( "PNG" );
1611                extensions.add( "GIF" );
1612                extensions.add( "TIF" );
1613                extensions.add( "TIFF" );
1614                extensions.add( "GEOTIFF" );
1615            }
1616    
1617            /**
1618             * @return "*.*"
1619             */
1620            public String getDescription() {
1621                return "*.*";
1622            }
1623    
1624            /*
1625             * (non-Javadoc)
1626             * 
1627             * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
1628             */
1629            public boolean accept( java.io.File file, String name ) {
1630                int pos = name.lastIndexOf( "." );
1631                String ext = name.substring( pos + 1 ).toUpperCase();
1632                if ( file.isDirectory() ) {
1633                    String s = file.getAbsolutePath() + '/' + name;
1634                    File tmp = new File( s );
1635                    if ( tmp.isDirectory() ) {
1636                        return true;
1637                    }
1638                }
1639                return extensions.contains( ext );
1640            }
1641        }
1642    
1643        /**
1644         * 
1645         * This class enables sorting of dBaseFile objects in chronological order (lowest first, highest
1646         * last).
1647         * 
1648         * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
1649         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1650         * @author last edited by: $Author: apoth $
1651         * 
1652         * @version 2.0, $Revision: 9681 $, $Date: 2008-01-23 09:55:12 +0100 (Mi, 23 Jan 2008) $
1653         * 
1654         * @since 2.0
1655         */
1656        private static class MapAgeComparator implements Comparator<Object> {
1657    
1658            private String direction = null;
1659    
1660            /**
1661             * @param direction
1662             */
1663            public MapAgeComparator( String direction ) {
1664                this.direction = direction.toUpperCase();
1665            }
1666    
1667            public int compare( Object o1, Object o2 ) {
1668                Object[] o1a = (Object[]) o1;
1669                Object[] o2a = (Object[]) o2;
1670    
1671                if ( o1a[0] == null || o2a[0] == null ) {
1672                    return 0;
1673                }
1674                if ( direction.equals( "UP" ) ) {
1675                    return o1a[0].toString().compareTo( o2a[0].toString() );
1676                }
1677                return o2a[0].toString().compareTo( o1a[0].toString() );
1678            }
1679        }
1680    
1681    }