001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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: apoth $
141     * 
142     * @version 2.0, $Revision: 20078 $, $Date: 2009-10-09 11:40:15 +0200 (Fr, 09. Okt 2009) $
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        public static GraphicsConfiguration getDefaultConfiguration() {
944            GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
945            GraphicsDevice gd = ge.getDefaultScreenDevice();
946            return gd.getDefaultConfiguration();
947        }
948    
949        public static BufferedImage resize( BufferedImage source, int destWidth, int destHeight, Object interpolation ) {
950    
951            int sourceWidth = source.getWidth();
952            int sourceHeight = source.getHeight();
953            double xScale = ( (double) destWidth ) / (double) sourceWidth;
954            double yScale = ( (double) destHeight ) / (double) sourceHeight;
955            if ( destWidth <= 0 ) {
956                xScale = yScale;
957                destWidth = (int) Math.rint( xScale * sourceWidth );
958            }
959            if ( destHeight <= 0 ) {
960                yScale = xScale;
961                destHeight = (int) Math.rint( yScale * sourceHeight );
962            }
963            GraphicsConfiguration gc = getDefaultConfiguration();
964            BufferedImage result = gc.createCompatibleImage( destWidth, destHeight,
965                                                             source.getColorModel().getTransparency() );
966            Graphics2D g2d = null;
967            try {
968                g2d = result.createGraphics();
969                g2d.setRenderingHint( RenderingHints.KEY_INTERPOLATION, interpolation );
970                AffineTransform at = AffineTransform.getScaleInstance( xScale, yScale );
971                g2d.drawRenderedImage( source, at );
972            } finally {
973                if ( g2d != null )
974                    g2d.dispose();
975            }
976            return result;
977        }
978    
979        private static boolean isTransparent( BufferedImage bi ) {
980            /*
981             * TODO determine if the passed image is completly transparent for ( int i = 0; i < bi.getHeight(); i++ ) { for
982             * ( int j = 0; j < bi.getWidth(); j++ ) { if ( bi.getRGB( i, j ) != 0 && bi.getRGB( i, j ) != -256 ) { return
983             * false; } } } return true;
984             */
985            return false;
986        }
987    
988        /**
989         * @return an interpolation object from a well known name
990         * @param interpolation
991         */
992        public static Object createInterpolation( String interpolation ) {
993            Object interpol = null;
994    
995            if ( interpolation.equalsIgnoreCase( "Nearest Neighbor" ) ) {
996                interpol = RenderingHints.VALUE_INTERPOLATION_NEAREST_NEIGHBOR;
997            } else if ( interpolation.equalsIgnoreCase( "Bicubic" ) ) {
998                interpol = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
999            } else if ( interpolation.equalsIgnoreCase( "Bicubic2" ) ) {
1000                // for downward compability
1001                interpol = RenderingHints.VALUE_INTERPOLATION_BICUBIC;
1002            } else if ( interpolation.equalsIgnoreCase( "Bilinear" ) ) {
1003                interpol = RenderingHints.VALUE_INTERPOLATION_BILINEAR;
1004            } else {
1005                throw new RuntimeException( "invalid interpolation method: " + interpolation );
1006            }
1007    
1008            return interpol;
1009        }
1010    
1011        /**
1012         * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of the output file is
1013         * defined by the <code>baseName</code> and the tile's index of row and column.
1014         * 
1015         * @param tile
1016         *            The tile for which to create a world file.
1017         * @throws IOException
1018         */
1019        private void createWorldFile( Tile tile )
1020                                throws IOException {
1021    
1022            Envelope env = tile.getTileEnvelope();
1023            String postfix = tile.getPostfix();
1024    
1025            StringBuffer sb = new StringBuffer( 1000 );
1026    
1027            sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 );
1028            sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" );
1029            sb.append( env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() );
1030            sb.append( "\n" );
1031    
1032            File f = new File( outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + ".wld" );
1033    
1034            FileWriter fw = new FileWriter( f );
1035            PrintWriter pw = new PrintWriter( fw );
1036    
1037            pw.print( sb.toString() );
1038    
1039            pw.close();
1040            fw.close();
1041        }
1042    
1043        /**
1044         * stores an envelope and the assigend image file information into a feature/featureCollection
1045         * 
1046         * @param dir
1047         *            directory where the image file is stored
1048         * @param file
1049         *            name of the image file
1050         * @param env
1051         *            bbox of the image file
1052         */
1053        private void storeEnvelope( String dir, String file, Envelope env ) {
1054            try {
1055                Geometry geom = GeometryFactory.createSurface( env, null );
1056                FeatureProperty[] props = new FeatureProperty[3];
1057                props[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), geom );
1058                props[1] = FeatureFactory.createFeatureProperty(
1059                                                                 new QualifiedName(
1060                                                                                    GridCoverageExchange.SHAPE_IMAGE_FILENAME ),
1061                                                                 file );
1062                props[2] = FeatureFactory.createFeatureProperty( new QualifiedName( GridCoverageExchange.SHAPE_DIR_NAME ),
1063                                                                 dir );
1064                Feature feat = FeatureFactory.createFeature( "file", ftype, props );
1065                fc.add( feat );
1066            } catch ( Exception e ) {
1067                e.printStackTrace();
1068            }
1069        }
1070    
1071        /**
1072         * creates a configuration file (extended CoverageDescriotion) for a WCS coverage considering the passed resolution
1073         * levels
1074         * 
1075         * @param targetResolutions
1076         */
1077        private void createConfigurationFile( double[] targetResolutions ) {
1078    
1079            // copy this file to the target directory
1080            String resolutions = "";
1081            sort( targetResolutions );
1082            int length = targetResolutions.length;
1083    
1084            for ( int i = 0; i < length; i++ ) {
1085                resolutions += String.valueOf( targetResolutions[length - 1 - i] );
1086                if ( i < ( length - 1 ) )
1087                    resolutions += ',';
1088            }
1089    
1090            try {
1091                Map<String, String> param = new HashMap<String, String>( 20 );
1092                Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1093                param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1094                                          + String.valueOf( llEnv.getMin().getY() ) );
1095                param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1096                                           + String.valueOf( llEnv.getMax().getY() ) );
1097                param.put( "upperleft", String.valueOf( combiningEnvelope.getMin().getX() ) + ','
1098                                        + String.valueOf( combiningEnvelope.getMin().getY() ) );
1099                param.put( "lowerright", String.valueOf( combiningEnvelope.getMax().getX() ) + ','
1100                                         + combiningEnvelope.getMax().getY() );
1101                File dir = new File( outputDir );
1102                if ( dir.isAbsolute() ) {
1103                    // param.put( "dataDir", outputDir + '/' );
1104                    param.put( "dataDir", "" );
1105                } else {
1106                    param.put( "dataDir", "" );
1107                }
1108                param.put( "label", baseName );
1109                param.put( "name", baseName );
1110                param.put( "description", "" );
1111                param.put( "keywords", "" );
1112                param.put( "resolutions", resolutions );
1113                String frm = outputFormat;
1114                if ( "raw".equals( outputFormat ) && bitDepth == 32 ) {
1115                    frm = "tif";
1116                } else if ( "raw".equals( outputFormat ) && bitDepth == 16 ) {
1117                    frm = "GeoTiff";
1118                }
1119                param.put( "mimeType", frm );
1120                int p = srs.lastIndexOf( ':' );
1121                param.put( "srs", srs.substring( p + 1, srs.length() ) );
1122                param.put( "srsPre", srs.substring( 0, p + 1 ) );
1123    
1124                Reader reader = new InputStreamReader( configURL.openStream() );
1125    
1126                XSLTDocument xslt = new XSLTDocument();
1127                xslt.load( configXSL );
1128                XMLFragment xml = xslt.transform( reader, XMLFragment.DEFAULT_URL, null, param );
1129                reader.close();
1130    
1131                // write the result
1132                String dstFilename = "wcs_" + baseName + "_configuration.xml";
1133                File dstFile = new File( outputDir, dstFilename );
1134                String configurationFilename = dstFile.getAbsolutePath().toString();
1135                FileOutputStream fos = new FileOutputStream( configurationFilename );
1136                xml.write( fos );
1137                fos.close();
1138    
1139            } catch ( Exception e1 ) {
1140                e1.printStackTrace();
1141            }
1142    
1143        }
1144    
1145        private Envelope getLatLonEnvelope( Envelope env )
1146                                throws Exception {
1147            GeoTransformer gt = new GeoTransformer( "EPSG:4326" );
1148            return gt.transform( env, srs );
1149        }
1150    
1151        /**
1152         *
1153         */
1154        private void updateCapabilitiesFile( File capabilitiesFile ) {
1155    
1156            try {
1157                XSLTDocument xslt = new XSLTDocument();
1158                xslt.load( inputXSL );
1159                Map<String, String> param = new HashMap<String, String>();
1160    
1161                param.put( "dataDirectory", outputDir );
1162                String url = new File( "wcs_" + baseName + "_configuration.xml" ).toURL().toString();
1163                param.put( "configFile", url );
1164                Envelope llEnv = getLatLonEnvelope( combiningEnvelope );
1165                param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ','
1166                                          + String.valueOf( llEnv.getMin().getY() ) );
1167                param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ','
1168                                           + String.valueOf( llEnv.getMax().getY() ) );
1169    
1170                param.put( "name", baseName );
1171                param.put( "label", baseName );
1172    
1173                param.put( "description", "" );
1174                param.put( "keywords", "" );
1175    
1176                XMLFragment xml = new XMLFragment();
1177                xml.load( capabilitiesFile.toURL() );
1178    
1179                xml = xslt.transform( xml, capabilitiesFile.toURL().toExternalForm(), null, param );
1180    
1181                // write the result
1182                FileOutputStream fos = new FileOutputStream( capabilitiesFile );
1183                xml.write( fos );
1184                fos.close();
1185            } catch ( Exception e ) {
1186                e.printStackTrace();
1187            }
1188        }
1189    
1190        /**
1191         * Validates the content of <code>map</code>, to see, if necessary arguments were passed when calling this class.
1192         * 
1193         * @param map
1194         * @throws Exception
1195         */
1196        private static void validate( Properties map )
1197                                throws Exception {
1198    
1199            if ( map.get( "-outDir" ) == null ) {
1200                throw new Exception( "-outDir must be set" );
1201            }
1202            String s = (String) map.get( "-outDir" );
1203            if ( s.endsWith( "/" ) || s.endsWith( "\\" ) ) {
1204                s = s.substring( 0, s.length() - 1 );
1205            }
1206    
1207            if ( map.get( "-baseName" ) == null ) {
1208                throw new Exception( "-baseName must be set" );
1209            }
1210            if ( map.get( "-outputFormat" ) == null ) {
1211                map.put( "-outputFormat", "png" );
1212            } else {
1213                String format = ( (String) map.get( "-outputFormat" ) ).toLowerCase();
1214                if ( !"bmp".equals( format ) && !"png".equals( format ) && !"jpg".equals( format )
1215                     && !"jpeg".equals( format ) && !"tif".equals( format ) && !"tiff".equals( format )
1216                     && !"gif".equals( format ) && !( "raw" ).equals( format ) ) {
1217    
1218                    throw new Exception( "-outputFormat must be one of the following: "
1219                                         + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'." );
1220                }
1221            }
1222            if ( map.get( "-maxTileSize" ) == null ) {
1223                map.put( "-maxTileSize", "500" );
1224            }
1225            if ( map.get( "-srs" ) == null ) {
1226                map.put( "-srs", "EPSG:4326" );
1227            }
1228            if ( map.get( "-interpolation" ) == null ) {
1229                map.put( "-interpolation", "Nearest Neighbor" );
1230            }
1231            if ( map.get( "-noOfLevel" ) == null ) {
1232                map.put( "-noOfLevel", "1" );
1233            }
1234            if ( map.get( "-worldFileType" ) == null ) {
1235                map.put( "-worldFileType", "center" );
1236            }
1237            if ( map.get( "-quality" ) == null ) {
1238                map.put( "-quality", "0.95" );
1239            }
1240            if ( map.get( "-bbox" ) != null ) {
1241                double[] d = StringTools.toArrayDouble( (String) map.get( "-bbox" ), "," );
1242                Envelope env = GeometryFactory.createEnvelope( d[0], d[1], d[2], d[3], null );
1243                map.put( "-bbox", env );
1244                if ( map.get( "-resolution" ) == null ) {
1245                    throw new Exception( "-resolution must be set if -bbox is set" );
1246                }
1247                map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1248            } else {
1249                if ( map.get( "-resolution" ) == null ) {
1250                    map.put( "-resolution", new Double( -1 ) );
1251                } else {
1252                    map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) );
1253                }
1254            }
1255        }
1256    
1257        /**
1258         * @return the list of image map files to consider read from -mapFiles parameter
1259         * 
1260         * @param mapFiles
1261         */
1262        private static List<String> getFileList( String[] mapFiles ) {
1263            List<String> imageFiles = new ArrayList<String>();
1264            for ( int i = 0; i < mapFiles.length; i++ ) {
1265                imageFiles.add( mapFiles[i] );
1266            }
1267            return imageFiles;
1268        }
1269    
1270        /**
1271         * @return the list of image map files to consider read from a defined root directory.
1272         * 
1273         * @param rootDir
1274         *            root directory where to read image map files
1275         * @param subdirs
1276         *            true if subdirectories of the root directory shall be parsed for image maps too
1277         */
1278        private static List<String> getFileList( String rootDir, boolean subdirs ) {
1279            List<String> list = new ArrayList<String>( 10000 );
1280            File file = new File( rootDir );
1281            String[] entries = file.list( new DFileFilter() );
1282            if ( entries != null ) {
1283                for ( int i = 0; i < entries.length; i++ ) {
1284                    File entry = new File( rootDir + '/' + entries[i] );
1285                    if ( entry.isDirectory() && subdirs ) {
1286                        list = readSubDirs( entry, list );
1287                    } else {
1288                        list.add( rootDir + '/' + entries[i] );
1289                    }
1290                }
1291            }
1292            return list;
1293        }
1294    
1295        /**
1296         * 
1297         * @param file
1298         * @param list
1299         * @return the sub directories
1300         */
1301        private static List<String> readSubDirs( File file, List<String> list ) {
1302    
1303            String[] entries = file.list( new DFileFilter() );
1304            if ( entries != null ) {
1305                for ( int i = 0; i < entries.length; i++ ) {
1306                    File entry = new File( file.getAbsolutePath() + '/' + entries[i] );
1307                    if ( entry.isDirectory() ) {
1308                        list = readSubDirs( entry, list );
1309                    } else {
1310                        list.add( file.getAbsolutePath() + '/' + entries[i] );
1311                    }
1312                }
1313            }
1314            return list;
1315        }
1316    
1317        /**
1318         * @return the list of image map files to consider read from a dbase file defined by the dbase parameter
1319         * 
1320         * @param dbaseFile
1321         *            name of the dbase file
1322         * @param fileColumn
1323         *            name of the column containing the image map files names
1324         * @param baseDir
1325         *            name of the directory where the image map files are stored if this parameter is <code>null</code> it
1326         *            is assumed that the image map files are full referenced within the dbase
1327         * @param sort
1328         *            true if map image file names shall be sorted
1329         * @param sortColum
1330         *            name of the column that shall be used for sorting
1331         */
1332        private static List<String> getFileList( String dBaseFile, String fileColumn, String baseDir, boolean sort,
1333                                                 String sortColum, String sortDirection )
1334                                throws Exception {
1335    
1336            // handle dbase file extension and file location/reading problems
1337            if ( dBaseFile.endsWith( ".dbf" ) ) {
1338                dBaseFile = dBaseFile.substring( 0, dBaseFile.lastIndexOf( "." ) );
1339            }
1340            DBaseFile dbf = new DBaseFile( dBaseFile );
1341    
1342            // sort dbase file contents chronologicaly (oldest first)
1343            int cnt = dbf.getRecordNum();
1344    
1345            Object[][] mapItems = new Object[cnt][2];
1346            QualifiedName fileC = new QualifiedName( APP_PREFIX, fileColumn.toUpperCase(), DEEGREEAPP );
1347            QualifiedName sortC = null;
1348            if ( sort ) {
1349                sortC = new QualifiedName( APP_PREFIX, sortColum.toUpperCase(), DEEGREEAPP );
1350            }
1351            for ( int i = 0; i < cnt; i++ ) {
1352                if ( sort ) {
1353                    mapItems[i][0] = dbf.getFRow( i + 1 ).getDefaultProperty( sortC ).getValue();
1354                } else {
1355                    mapItems[i][0] = new Integer( 1 );
1356                }
1357                // name of map file
1358                mapItems[i][1] = dbf.getFRow( i + 1 ).getDefaultProperty( fileC ).getValue();
1359            }
1360            Arrays.sort( mapItems, new MapAgeComparator( sortDirection ) );
1361    
1362            // extract names of image files from dBase file and attach them to rootDir
1363            if ( baseDir == null ) {
1364                baseDir = "";
1365            } else if ( !baseDir.endsWith( "/" ) && !baseDir.endsWith( "\\" ) ) {
1366                baseDir = baseDir + "/";
1367            }
1368            List<String> imageFiles = new ArrayList<String>( mapItems.length );
1369            for ( int i = 0; i < mapItems.length; i++ ) {
1370                if ( mapItems[i][0] != null ) {
1371                    LOG.logDebug( "" + mapItems[i][0] );
1372                    imageFiles.add( baseDir + mapItems[i][1] );
1373                }
1374            }
1375    
1376            return imageFiles;
1377        }
1378    
1379        private static void printHelp() {
1380    
1381            System.out.println( "-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n"
1382                                + "-redirect whether to redirect the standard output/error streams to a file rtb.log in the output directory. Default is false.\r\n"
1383                                + "-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"
1384                                + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n"
1385                                + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n"
1386                                + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n"
1387                                + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n"
1388                                + "               be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n"
1389                                + "               If you use the proogram with images (tif) containing raw data like DEMs just use \r\n"
1390                                + "               Nearest Neighbor interpolation. All other interpolation methods will cause artefacts."
1391                                + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n"
1392                                + "-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"
1393                                + "-noOfLevel number of tree levels created (optional default = 1)\r\n"
1394                                + "-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"
1395                                + "-h or -? print this help\r\n"
1396                                + "\r\n"
1397                                + "Input files\r\n"
1398                                + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n"
1399                                + "1)\r\n"
1400                                + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n"
1401                                + "\r\n"
1402                                + "2)\r\n"
1403                                + "-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"
1404                                + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n"
1405                                + "\r\n"
1406                                + "3)\r\n"
1407                                + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n"
1408                                + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n"
1409                                + "-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"
1410                                + "-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"
1411                                + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n"
1412                                + "-worldFileType two types of are common: \r\n "
1413                                + "               a) the boundingbox is defined on the center of the corner pixels; \r\n "
1414                                + "               b) the boundingbox is defined on the outer corner of the corner pixels; \r\n "
1415                                + "               first is default and will be used if this parameter is not set; second will be use if '-worldFileType outer' is defined.\r\n"
1416                                + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n"
1417                                + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n"
1418                                + "-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"
1419                                + "         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"
1420                                + "-offset defines the offset added to raster values if -outputFormat = raw and -bitDepth (default 0) \r\n"
1421                                + "-scaleFactor defines the factor by which raster values are multiplied if -outputFormat = raw and -bitDepth (default 1) \r\n"
1422                                + "\r\n"
1423                                + "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." );
1424            System.out.println();
1425            System.out.println( "caching:" );
1426            System.out.println( "To use default caching mechanism you just have to start RTB as before; to define " );
1427            System.out.println( "your own caching behavior you have to place a file named ehcache.xml within the " );
1428            System.out.println( "root of your classpath. The content of this file is described by the ehcache documentation " );
1429            System.out.println( "(http://ehcache.sourceforge.net/documentation); at least it must provide a cache named 'imgCache'." );
1430            System.out.println( " When defining your own cache please consider that just 'inMemory' caching is supported because " );
1431            System.out.println( "the objects cached by RTB are not serializable." );
1432            System.out.println();
1433            System.out.println( "Example invoking RTB (windows):" );
1434            System.out.println( "java -Xms300m -Xmx1000m -classpath .;.\\lib\\deegree2.jar;.\\lib\\acme.jar;"
1435                                + ".\\lib\\batik-awt-util.jar;.\\lib\\commons-beanutils-1.5.jar;"
1436                                + ".\\lib\\commons-codec-1.3.jar;.\\lib\\commons-collections-3.1.jar;"
1437                                + ".\\lib\\commons-digester-1.7.jar;.\\lib\\commons-discovery-0.2.jar;"
1438                                + ".\\lib\\commons-logging.jar;.\\lib\\jai_codec.jar;.\\lib\\jai_core.jar;"
1439                                + ".\\lib\\mlibwrapper_jai.jar;.\\lib\\j3dcore.jar;.\\lib\\j3dutils.jar;"
1440                                + ".\\lib\\vecmath.jar;.\\lib\\jts-1.6.jar;.\\lib\\log4j-1.2.9.jar;"
1441                                + ".\\lib\\axis.jar;.\\lib\\jaxen-1.1-beta-7.jar;.\\lib\\ehcache-1.2.0_03.jar "
1442                                + "org.deegree.tools.raster.RasterTreeBuilder "
1443                                + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ "
1444                                + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation "
1445                                + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn "
1446                                + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir "
1447                                + "D:/lgv/resources/data/images/ " );
1448        }
1449    
1450        /**
1451         * 
1452         * @param args
1453         *            Example arguments to pass when calling are:
1454         *            <ul>
1455         *            <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li>
1456         *            <li>-outDir D:/temp/out/</li>
1457         *            <li>-baseName pretty</li>
1458         *            <li>-outputFormat png</li>
1459         *            <li>-maxTileSize 600</li>
1460         *            </ul>
1461         * 
1462         * @throws Exception
1463         */
1464        public static void main( String[] args )
1465                                throws Exception {
1466    
1467            Properties map = new Properties();
1468            for ( int i = 0; i < args.length; i += 2 ) {
1469                map.put( args[i], args[i + 1] );
1470            }
1471    
1472            if ( map.get( "-?" ) != null || map.get( "-h" ) != null ) {
1473                printHelp();
1474                return;
1475            }
1476    
1477            try {
1478                validate( map );
1479            } catch ( Exception e ) {
1480                LOG.logInfo( map.toString() );
1481                System.out.println( e.getMessage() );
1482                System.out.println();
1483                printHelp();
1484                return;
1485            }
1486    
1487            String outDir = map.getProperty( "-outDir" );
1488    
1489            // set up stderr/stdout redirection
1490            String redirect = map.getProperty( "-redirect" );
1491            if ( redirect != null && redirect.equals( "true" ) ) {
1492                File f = new File( outDir + separator + "rtb.log" );
1493                PrintStream out = new PrintStream( new FileOutputStream( f ) );
1494                System.setOut( out );
1495                System.setErr( out );
1496            }
1497    
1498            // read input parameters
1499            String baseName = map.getProperty( "-baseName" );
1500            String outputFormat = map.getProperty( "-outputFormat" );
1501            String srs = map.getProperty( "-srs" );
1502            if ( srs == null ) {
1503                srs = "EPSG:4326";
1504            }
1505            String interpolation = map.getProperty( "-interpolation" );
1506            Envelope env = (Envelope) map.get( "-bbox" );
1507            double resolution = ( (Double) map.get( "-resolution" ) ).doubleValue();
1508            int level = Integer.parseInt( map.getProperty( "-noOfLevel" ) );
1509            double maxTileSize = ( Double.valueOf( map.getProperty( "-maxTileSize" ) ) ).doubleValue();
1510            WorldFile.TYPE worldFileType = WorldFile.TYPE.CENTER;
1511            if ( "outer".equals( map.getProperty( "-worldFileType" ) ) ) {
1512                worldFileType = WorldFile.TYPE.OUTER;
1513            }
1514            float quality = Float.parseFloat( map.getProperty( "-quality" ) );
1515            String backgroundColor = map.getProperty( "-bgColor" );
1516    
1517            int depth = 0;
1518    
1519            if ( map.get( "-bitDepth" ) != null ) {
1520                depth = Integer.parseInt( map.getProperty( "-bitDepth" ) );
1521            }
1522    
1523            boolean dummy = false;
1524            if ( map.get( "-dummy" ) != null ) {
1525                dummy = true;
1526            }
1527    
1528            float offset = 0;
1529            if ( map.get( "-offset" ) != null ) {
1530                offset = Float.parseFloat( map.getProperty( "-offset" ) );
1531            }
1532    
1533            float scaleFactor = 1;
1534            if ( map.get( "-scaleFactor" ) != null ) {
1535                scaleFactor = Float.parseFloat( map.getProperty( "-scaleFactor" ) );
1536            }
1537    
1538            List<String> imageFiles = null;
1539            if ( map.get( "-mapFiles" ) != null ) {
1540                String[] mapFiles = StringTools.toArray( map.getProperty( "-mapFiles" ), ",;|", true );
1541                imageFiles = getFileList( mapFiles );
1542            } else if ( map.get( "-dbaseFile" ) != null ) {
1543                String dBaseFile = map.getProperty( "-dbaseFile" );
1544                String fileColum = map.getProperty( "-fileColumn" );
1545                String baseDir = map.getProperty( "-baseDir" );
1546                if ( baseDir == null ) {
1547                    baseDir = map.getProperty( "-rootDir" );
1548                }
1549                boolean sort = map.get( "-sortColumn" ) != null;
1550                String sortColumn = map.getProperty( "-sortColumn" );
1551                if ( map.get( "-sortDirection" ) == null ) {
1552                    map.put( "-sortDirection", "UP" );
1553                }
1554                String sortDirection = map.getProperty( "-sortDirection" );
1555                imageFiles = getFileList( dBaseFile, fileColum, baseDir, sort, sortColumn, sortDirection );
1556            } else if ( map.get( "-rootDir" ) != null ) {
1557                String rootDir = map.getProperty( "-rootDir" );
1558                boolean subDirs = "true".equals( map.get( "-subDirs" ) );
1559                imageFiles = getFileList( rootDir, subDirs );
1560            } else {
1561                LOG.logInfo( map.toString() );
1562                System.out.println( "-mapFiles, -rootDir or -dbaseFile parameter must be defined" );
1563                printHelp();
1564                return;
1565            }
1566    
1567            LOG.logDebug( imageFiles.toString() );
1568            LOG.logInfo( map.toString() );
1569    
1570            // initialize RasterTreeBuilder
1571            RasterTreeBuilder rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1572                                                           interpolation, worldFileType, quality, backgroundColor, depth,
1573                                                           resolution, offset, scaleFactor, dummy );
1574    
1575            // calculate bbox and resolution from input images if parameters are not set
1576            if ( env == null ) {
1577                WorldFile wf = rtb.determineCombiningBBox();
1578                env = wf.getEnvelope();
1579                resolution = wf.getResx();
1580            }
1581    
1582            // Calculate necessary number of levels to get not more than 4
1583            // tiles in highest resolution
1584            if ( level == -1 ) {
1585                rtb.init( env, resolution );
1586                level = 0;
1587                int numTilesMax = Math.min( rtb.tileCols, rtb.tileRows );
1588                int numTiles = 4;
1589                while ( numTiles < numTilesMax ) {
1590                    level += 1;
1591                    numTiles *= 2;
1592                }
1593            }
1594            if ( level == 0 ) {
1595                level = 1;
1596            }
1597            System.out.println( "Number of Levels: " + level );
1598    
1599            // create tree where for each loop resolution will be halfed
1600            double[] re = new double[level];
1601            for ( int i = 0; i < level; i++ ) {
1602                rtb.init( env, resolution );
1603                rtb.start();
1604                rtb.logCollectedErrors();
1605                re[i] = resolution;
1606                if ( i < level - 1 ) {
1607                    String dir = outDir + '/' + Double.toString( resolution );
1608                    imageFiles = getFileList( dir, false );
1609                    rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs,
1610                                                 interpolation, WorldFile.TYPE.CENTER, quality, backgroundColor, depth,
1611                                                 resolution, offset, scaleFactor );
1612                }
1613                resolution = resolution * 2;
1614            }
1615    
1616            LOG.logInfo( "create configuration files ..." );
1617            rtb.createConfigurationFile( re );
1618    
1619            if ( map.get( "-capabilitiesFile" ) != null ) {
1620                LOG.logInfo( "adjust capabilities ..." );
1621                File file = new File( map.getProperty( "-capabilitiesFile" ) );
1622                rtb.updateCapabilitiesFile( file );
1623            }
1624    
1625            rtb.logCollectedErrors();
1626        }
1627    
1628        /**
1629         * class: official version of a FilenameFilter
1630         */
1631        static class DFileFilter implements FilenameFilter {
1632    
1633            private List<String> extensions = null;
1634    
1635            /**
1636             *
1637             */
1638            public DFileFilter() {
1639                extensions = new ArrayList<String>();
1640                extensions.add( "JPEG" );
1641                extensions.add( "JPG" );
1642                extensions.add( "BMP" );
1643                extensions.add( "PNG" );
1644                extensions.add( "GIF" );
1645                extensions.add( "TIF" );
1646                extensions.add( "TIFF" );
1647                extensions.add( "GEOTIFF" );
1648            }
1649    
1650            /**
1651             * @return "*.*"
1652             */
1653            public String getDescription() {
1654                return "*.*";
1655            }
1656    
1657            /*
1658             * (non-Javadoc)
1659             * 
1660             * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String)
1661             */
1662            public boolean accept( java.io.File file, String name ) {
1663                int pos = name.lastIndexOf( "." );
1664                String ext = name.substring( pos + 1 ).toUpperCase();
1665                if ( file.isDirectory() ) {
1666                    String s = file.getAbsolutePath() + '/' + name;
1667                    File tmp = new File( s );
1668                    if ( tmp.isDirectory() ) {
1669                        return true;
1670                    }
1671                }
1672                return extensions.contains( ext );
1673            }
1674        }
1675    
1676        /**
1677         * 
1678         * This class enables sorting of dBaseFile objects in chronological order (lowest first, highest last).
1679         * 
1680         * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a>
1681         * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
1682         * @author last edited by: $Author: apoth $
1683         * 
1684         * @version 2.0, $Revision: 20078 $, $Date: 2009-10-09 11:40:15 +0200 (Fr, 09. Okt 2009) $
1685         * 
1686         * @since 2.0
1687         */
1688        private static class MapAgeComparator implements Comparator<Object> {
1689    
1690            private String direction = null;
1691    
1692            /**
1693             * @param direction
1694             */
1695            public MapAgeComparator( String direction ) {
1696                this.direction = direction.toUpperCase();
1697            }
1698    
1699            public int compare( Object o1, Object o2 ) {
1700                Object[] o1a = (Object[]) o1;
1701                Object[] o2a = (Object[]) o2;
1702    
1703                if ( o1a[0] == null || o2a[0] == null ) {
1704                    return 0;
1705                }
1706                if ( direction.equals( "UP" ) ) {
1707                    return o1a[0].toString().compareTo( o2a[0].toString() );
1708                }
1709                return o2a[0].toString().compareTo( o1a[0].toString() );
1710            }
1711        }
1712    
1713    }