    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    }