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