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