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