//$Header$ /*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2006 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/deegree/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstraße 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.tools.raster; import java.awt.Color; import java.awt.Graphics; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.WritableRaster; import java.awt.image.renderable.ParameterBlock; import java.io.File; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.Reader; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import javax.media.jai.Interpolation; import javax.media.jai.InterpolationBicubic; import javax.media.jai.InterpolationBicubic2; import javax.media.jai.InterpolationBilinear; import javax.media.jai.InterpolationNearest; import javax.media.jai.JAI; import javax.media.jai.RenderedOp; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.framework.util.ImageUtils; import org.deegree.framework.util.StringTools; import org.deegree.framework.xml.XMLFragment; import org.deegree.framework.xml.XSLTDocument; import org.deegree.graphics.transformation.GeoTransform; import org.deegree.graphics.transformation.WorldToScreenTransform; import org.deegree.io.dbaseapi.DBaseFile; import org.deegree.io.shpapi.ShapeFile; import org.deegree.model.coverage.grid.GridCoverageExchangeIm; import org.deegree.model.coverage.grid.WorldFile; import org.deegree.model.crs.GeoTransformer; import org.deegree.model.crs.IGeoTransformer; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.Geometry; import org.deegree.model.spatialschema.GeometryFactory; import org.deegree.ogcbase.CommonNamespaces; import com.sun.media.jai.codec.FileSeekableStream; /** * This class represents a <code>RasterTreeBuilder</code> object. * TODO more info * * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a> * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version 2.0, $Revision$, $Date$ * * @since 2.0 */ public class RasterTreeBuilder { private static final ILogger LOG = LoggerFactory.getLogger( RasterTreeBuilder.class ); private static final URI DEEGREEAPP = CommonNamespaces.buildNSURI( "http://www.deegree.org/app" ); private static final String APP_PREFIX = "app"; // templates and transformation scripts private URL configURL = RasterTreeBuilder.class.getResource( "template_wcs_configuration.xml" ); private URL configXSL = RasterTreeBuilder.class.getResource( "updateConfig.xsl" ); private URL inputXSL = RasterTreeBuilder.class.getResource( "updateCapabilities.xsl" ); private int bitDepth = 16; private boolean geoTiff = false; // input for new MergeRaste object private List imageFiles; private List imageFilesEnvs; private Map imageFilesErrors; private ImageCache imageCache = null; private String outputDir; private String baseName; private String outputFormat; private double maxTileSize; private String srs = null; private Interpolation interpolation = null; private String worldFileType = null; private float quality = 0; private String bgColor = null; // minimum resolution of input images private double minimumRes; // combining image bounding box private Envelope combiningEnvelope; // size of virtual bounding box in px private long pxWidthVirtualBBox; private long pxHeightVirtualBBox; // size of every tile in virtual bounding box in px private long pxWidthTile; private long pxHeightTile; // number of tiles in virtual bounding box private int tileRows; private int tileCols; private FeatureType ftype = null; private FeatureCollection fc = null; /** * * @param imageFiles * @param outputDir * @param baseName * @param outputFormat * @param maxTileSize * @param srs * @param interpolation * @param worldFileType * @param quality * @param cacheSize * @param bgColor */ public RasterTreeBuilder( List imageFiles, String outputDir, String baseName, String outputFormat, double maxTileSize, String srs, String interpolation, String worldFileType, float quality, int cacheSize, String bgColor, int depth ) { this.imageFiles = imageFiles; this.imageFilesErrors = new HashMap( imageFiles.size() ); this.imageFilesEnvs = new ArrayList( imageFiles.size() ); for ( int i = 0; i < imageFiles.size(); i++ ) { this.imageFilesEnvs.add( null ); } this.outputDir = outputDir; File dir = new File( outputDir ).getAbsoluteFile(); if ( !dir.exists() ) { dir.mkdir(); } this.baseName = baseName; this.outputFormat = outputFormat.toLowerCase(); this.maxTileSize = maxTileSize; this.srs = srs; this.interpolation = createInterpolation( interpolation ); this.worldFileType = worldFileType; this.quality = quality; this.bgColor = bgColor; if ( depth != 0 ) { this.bitDepth = depth; } imageCache = new ImageCache( cacheSize ); PropertyType[] ftp = new PropertyType[3]; ftp[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false ); ftp[1] = FeatureFactory.createSimplePropertyType( new QualifiedName( GridCoverageExchangeIm.SHAPE_IMAGE_FILENAME ), Types.VARCHAR, false ); ftp[2] = FeatureFactory.createSimplePropertyType( new QualifiedName( GridCoverageExchangeIm.SHAPE_DIR_NAME ), Types.VARCHAR, false ); ftype = FeatureFactory.createFeatureType( new QualifiedName( "tiles" ), false, ftp ); } /** * @throws IOException */ public void logCollectedErrors() throws IOException { FileOutputStream fos = new FileOutputStream( "RasterTreeBuilder" + minimumRes + ".log" ); PrintWriter pw = new PrintWriter( fos ); pw.println( "processing the following files caused an error" ); Iterator iter = imageFilesErrors.keySet().iterator(); while ( iter.hasNext() ) { String key = (String) iter.next(); String value = (String) imageFilesErrors.get( key ); pw.print( key ); pw.print( ": " ); pw.println( value ); } pw.close(); LOG.logInfo( "LOG file RasterTreeBuilder.log has been written" ); } /** * starts creating of a raster tile level using the current bbox and * resolution * @throws Exception */ public void start() throws Exception { System.gc(); fc = FeatureFactory.createFeatureCollection( Double.toString( minimumRes ), tileRows * tileCols ); createTiles( tileRows, tileCols ); LOG.logInfo( "creating shape for georeferencing ... " ); ShapeFile sf = new ShapeFile( outputDir + "/sh" + minimumRes, "rw" ); sf.writeShape( fc ); sf.close(); } public void init( Envelope env, double resolution ) { // set target envelope setEnvelope( env ); setResolution( resolution ); determineVirtualBBox(); determineTileSize(); } /** * sets the resolution level to be used for tiling * @param resolution */ public void setResolution( double resolution ) { minimumRes = resolution; } /** * sets the bounding box used for tiling * @param bbox */ public void setEnvelope( Envelope bbox ) { combiningEnvelope = bbox; } /** * TODO this is a copy from org.deegree.tools.raster#AutoTiler * * Extracts the GeoKeys of the GeoTIFF. The Following Tags will be extracted * (http://www.remotesensing.org/geotiff/spec/geotiffhome.html): * ModelPixelScaleTag = 33550 (SoftDesk) * ModelTiepointTag = 33922 (Intergraph) * implementation status: working */ // private WorldFile readBBoxFromGeoTIFF( RenderedOp rop ) { // // TIFFDirectory tifDir = (TIFFDirectory) rop.getDynamicProperty( "tiff_directory" ); // // TIFFField modelPixelScaleTag = tifDir.getField( GeoTiffTag.ModelPixelScaleTag ); // double resx = modelPixelScaleTag.getAsDouble( 0 ); // double resy = modelPixelScaleTag.getAsDouble( 1 ); // TIFFField modelTiepointTag = tifDir.getField( GeoTiffTag.ModelTiepointTag ); // // double val1 = 0.0; // val1 = modelTiepointTag.getAsDouble( 0 ); // double val2 = 0.0; // val2 = modelTiepointTag.getAsDouble( 1 ); // double val4 = 0.0; // val4 = modelTiepointTag.getAsDouble( 3 ); // double val5 = 0.0; // val5 = modelTiepointTag.getAsDouble( 4 ); // // Envelope envelope = null; // if ( ( resx == 0.0 || resy == 0.0 ) // || ( val1 == 0.0 && val2 == 0.0 && val4 == 0.0 && val5 == 0.0 ) ) { // throw new RuntimeException( "The image/coverage has no bounding box." ); // // set the geoparams derived by geoTiffTags // } // // upper/left pixel // double xOrigin = val4 - ( val1 * resx ); // double yOrigin = val5 - ( val2 * resy ); // // lower/right pixel // double xRight = xOrigin + rop.getWidth() * resx; // double yBottom = yOrigin - rop.getHeight() * resy; // // envelope = GeometryFactory.createEnvelope( xOrigin, yBottom, xRight, yOrigin, null ); // // return new WorldFile( resx, resy, 0, 0, envelope ); // } /** * TODO this is a copy from org.deegree.tools.raster#AutoTiler * * the following TIFFKeys count as indicator if a TIFF-File carries GeoTIFF information: * ModelPixelScaleTag = 33550 (SoftDesk) * ModelTransformationTag = 34264 (JPL Carto Group) * ModelTiepointTag = 33922 (Intergraph) * GeoKeyDirectoryTag = 34735 (SPOT) * GeoDoubleParamsTag = 34736 (SPOT) * GeoAsciiParamsTag = 34737 (SPOT) * implementation status: working */ // private boolean isGeoTIFFFormat( RenderedOp rop ) { // // TIFFDirectory tifDir = (TIFFDirectory) rop.getDynamicProperty( "tiff_directory" ); // // definition of a geotiff // if ( tifDir.getField( GeoTiffTag.ModelPixelScaleTag ) == null // && tifDir.getField( GeoTiffTag.ModelTransformationTag ) == null // && tifDir.getField( GeoTiffTag.ModelTiepointTag ) == null // && tifDir.getField( GeoTiffTag.GeoKeyDirectoryTag ) == null // && tifDir.getField( GeoTiffTag.GeoDoubleParamsTag ) == null // && tifDir.getField( GeoTiffTag.GeoAsciiParamsTag ) == null ) { // // return false; // // } // // is a geotiff and possibly might need to be treated as raw data // TIFFField bitsPerSample = tifDir.getField( GeoTiffTag.BitsPerSample ); // // if ( bitsPerSample != null ) { // int samples = bitsPerSample.getAsInt( 0 ); // if ( samples == 16 ) { // bitDepth = 16; // } // if ( samples == 32 ) { // bitDepth = 32; // } // } // // // check the EPSG number // TIFFField ff = tifDir.getField( GeoTiffTag.GeoKeyDirectoryTag ); // if ( ff == null ) { // return false; // } // // char[] ch = ff.getAsChars(); // // // resulting HashMap, containing the key and the array of values // HashMap geoKeyDirectoryTag = new HashMap( ff.getCount() / 4 ); // // array of values. size is 4-1. // int keydirversion, keyrevision, minorrevision, numberofkeys = -99; // // for ( int i = 0; i < ch.length; i = i + 4 ) { // int[] keys = new int[3]; // keydirversion = ch[i]; // // keyrevision = ch[i + 1]; // minorrevision = ch[i + 2]; // numberofkeys = ch[i + 3]; // keys[0] = keyrevision; // keys[1] = minorrevision; // keys[2] = numberofkeys; // // geoKeyDirectoryTag.put( new Integer( keydirversion ), keys ); // } // // return true; // // } /** * TODO this is a copy from org.deegree.tools.raster#AutoTiler * * loads the base image * @throws IOException */ private Object[] loadImage( String imageSource ) throws IOException { System.out.println( "read image: " + imageSource ); BufferedImage bi = null; FileSeekableStream fss = new FileSeekableStream( imageSource ); RenderedOp rop = JAI.create( "stream", fss ); bi = rop.getAsBufferedImage(); try { fss.close(); } catch ( IOException e ) { // should never happen } Object[] o = new Object[2]; o[0] = bi; // place holder for later usage o[1] = null; return o; } /** * Determins the necessary size of a bounding box, which is large enough to hold all * input image files. The result is stored in the combining <code>Envelope</code>. * * @throws Exception */ private WorldFile determineCombiningBBox() throws Exception { System.out.println( "calculating overall bounding box ..." ); if ( imageFiles == null || imageFiles.isEmpty() ) { throw new Exception( "No combining BoundingBox to be determined: " + "The list of image files is null or empty." ); } WorldFile wf1 = null; if ( combiningEnvelope == null ) { // upper left corner of combining bounding box double minX = Double.MAX_VALUE; double maxY = Double.MIN_VALUE; // lower right corner of combining bounding box double maxX = Double.MIN_VALUE; double minY = Double.MAX_VALUE; // minimum resolution within combining bounding box double minResX = Double.MAX_VALUE; double minResY = Double.MAX_VALUE; for ( int i = 0; i < imageFiles.size(); i++ ) { File file = new File( (String) imageFiles.get( i ) ); if ( file.exists() && !file.isDirectory() ) { // for faster clean up System.gc(); Object[] o = loadImage( (String) imageFiles.get( i ) ); BufferedImage bi = (BufferedImage) o[0]; imageCache.putImage( (String) imageFiles.get( i ), bi ); WorldFile wf = (WorldFile) o[1]; if ( !geoTiff ) { try { wf = WorldFile.readWorldFile( (String) imageFiles.get( i ), worldFileType, bi ); } catch ( Exception e ) { LOG.logError( e.getMessage() ); continue; } } // now the values of resx, resy, envelope of the current image file are available // find min for x and y minX = Math.min( minX, wf.getEnvelope().getMin().getX() ); minY = Math.min( minY, wf.getEnvelope().getMin().getY() ); // find max for x and y maxX = Math.max( maxX, wf.getEnvelope().getMax().getX() ); maxY = Math.max( maxY, wf.getEnvelope().getMax().getY() ); // find min for resolution of x and y minResX = Math.min( minResX, wf.getResx() ); minResY = Math.min( minResY, wf.getResy() ); } else { System.out.println( "File: " + imageFiles.get( i ) + " does not exist!" ); System.out.println( "Image will be ignored" ); } } // store minimum resolution minimumRes = Math.min( minResX, minResY ); combiningEnvelope = GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null ); } wf1 = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope ); return wf1; } /** * Determins a usefull size for the virtual bounding box. It is somewhat larger * than the combining bounding box. The result is stored in the virtual <code>Envelope</code>. * */ private Envelope determineVirtualBBox() { WorldFile wf = new WorldFile( minimumRes, minimumRes, 0, 0, combiningEnvelope ); double width = combiningEnvelope.getWidth(); double height = combiningEnvelope.getHeight(); long pxWidth = Math.round( width / minimumRes ); long pxHeight = Math.round( height / minimumRes ); // set width and height to next higher even-numbered thousand pxWidth = (long) ( maxTileSize * (long) Math.ceil( pxWidth / maxTileSize ) ); pxHeight = (long) ( maxTileSize * (long) Math.ceil( pxHeight / maxTileSize ) ); pxWidthVirtualBBox = pxWidth; pxHeightVirtualBBox = pxHeight; // upper left corner of virtual bounding box double minX = combiningEnvelope.getMin().getX(); double maxY = combiningEnvelope.getMax().getY(); // lower right corner of virtual bounding box double maxX = minX + ( pxWidth * wf.getResx() ); double minY = maxY - ( pxHeight * wf.getResx() ); return GeometryFactory.createEnvelope( minX, minY, maxX, maxY, null ); } /** * This method determins and sets the size of the tiles in pixel both horizontally (pxWidthTile) * and vertically (pxHeightTile). * It also sets the necessary number of <code>tileCols</code> (depending on the tileWidth) * and <code>tileRows</code> (depending on the tileHeight). * * By default, all tiles have a size of close to but less than 6000 pixel either way. */ private void determineTileSize() { /* * The size of the virtual bbox gets divided by maxTileSize to find an approximat number of * tiles (a). * * If the virtual bbox is in any direction (horizontally or vertically) smaler than * maxTileSize px, then it has only 1 tile in that direction. In this case, the size of the tile * equals the size of the virtual bbox. * * Otherwise, use 'tileCols' and 'tileRows' as the next larger integer to 'a' to get the * minimum number of tiles. Increase this number until the tile size becomes a whole number. * * Divide the size of the virtual bbox by the final number of tiles to get its tile size. * */ double a; int tileCols, tileRows; // determin width of tile a = ( pxWidthVirtualBBox / maxTileSize ); tileCols = (int) Math.ceil( a ); if ( a <= 1.0 ) { pxWidthTile = pxWidthVirtualBBox; } else { while ( pxWidthVirtualBBox % tileCols > 0 ) { tileCols++; } pxWidthTile = pxWidthVirtualBBox / tileCols; } // determin height of tile a = ( pxHeightVirtualBBox / maxTileSize ); tileRows = (int) Math.ceil( a ); if ( a <= 1.0 ) { pxHeightTile = pxHeightVirtualBBox; } else { while ( pxHeightVirtualBBox % tileRows > 0 ) { tileRows++; } pxHeightTile = pxHeightVirtualBBox / tileRows; } this.tileCols = tileCols; this.tileRows = tileRows; System.out.println( "minimum resolution: " + minimumRes ); System.out.println( "width = " + pxWidthVirtualBBox + " *** height = " + pxHeightVirtualBBox ); System.out.println( "pxWidthTile = " + pxWidthTile + " *** pxHeightTile = " + pxHeightTile ); System.out.println( "number of tiles: horizontally = " + tileCols + ", vertically = " + tileRows ); } /** * Creates one <code>Tile</code> object after the other, with the number of tiles being * specified by the given number of <code>rows</code> and <code>cols</code>. * * Each Tile gets written to the FileOutputStream by the internal call to #paintImagesOnTile. * * @param rows * @param cols * @throws IOException * @throws Exception */ private void createTiles( int rows, int cols ) throws IOException { System.out.println( "creating merged image ..." ); Tile tile = null; Envelope virtualEnv = determineVirtualBBox(); double tileWidth = virtualEnv.getWidth() / cols; double tileHeight = virtualEnv.getHeight() / rows; double leftX; double upperY = virtualEnv.getMax().getY(); double rightX; double lowerY; File file = new File( outputDir + "/" + Double.toString( minimumRes ) ).getAbsoluteFile(); file.mkdir(); for ( int i = 0; i < rows; i++ ) { System.gc(); System.out.println( "processing row " + i ); leftX = virtualEnv.getMin().getX(); lowerY = upperY - tileHeight; for ( int j = 0; j < cols; j++ ) { System.out.println( "processing tile: " + i + " - " + j ); rightX = leftX + tileWidth; Envelope env = GeometryFactory.createEnvelope( leftX, lowerY, rightX, upperY, null ); leftX = rightX; String postfix = "_" + i + "_" + j; tile = new Tile( env, postfix ); paintImagesOnTile( tile ); } upperY = lowerY; } System.gc(); } /** * Paints all image files that intersect with the passed <code>tile</code> onto that tile * and creates an output file in the <code>outputDir</code>. If no image file intersects with * the given tile, then an empty output file is created. The name of the output file is defined * by the <code>baseName</code> and the tile's index of row and column. * * @param tile The tile on which to paint the image. * @throws IOException */ private void paintImagesOnTile( Tile tile ) throws IOException { Envelope tileEnv = tile.getTileEnvelope(); String postfix = tile.getPostfix(); BufferedImage out = createOutputImage(); if ( bgColor != null ) { Graphics g = out.getGraphics(); g.setColor( Color.decode( bgColor ) ); g.fillRect( 0, 0, out.getWidth(), out.getHeight() ); g.dispose(); } boolean paint = false; int gcc = 0; for ( int i = 0; i < imageFiles.size(); i++ ) { File file = new File( (String) imageFiles.get( i ) ); if ( imageFilesErrors.get( imageFiles.get( i ) ) == null && file.exists() && !file.isDirectory() ) { WorldFile wf = (WorldFile) imageFilesEnvs.get( i ); BufferedImage bi = null; if ( wf == null ) { // just read image if bbox has not been already read Object[] o = loadImage( (String) imageFiles.get( i ) ); bi = (BufferedImage) o[0]; wf = (WorldFile) o[1]; if ( !geoTiff ) { try { wf = WorldFile.readWorldFile( (String) imageFiles.get( i ), worldFileType, bi ); } catch ( Exception e ) { imageFilesErrors.put( imageFiles.get( i ), e.getMessage() ); continue; } } // cache bounding boxes imageFilesEnvs.set( i, wf ); } // now the values of resx, resy, envelope of the current image file are available if ( wf.getEnvelope().intersects( tileEnv ) ) { if ( bi == null ) { bi = imageCache.getImage( (String) imageFiles.get( i ) ); } if ( bi == null ) { Object[] o = loadImage( (String) imageFiles.get( i ) ); bi = (BufferedImage) o[0]; imageCache.putImage( (String) imageFiles.get( i ), bi ); if ( gcc % 5 == 0 ) { System.gc(); } gcc++; } try { drawImage( out, bi, tile, wf ); paint = true; } catch ( Exception e ) { e.printStackTrace(); imageFilesErrors.put( imageFiles.get( i ), e.getMessage() ); } } } else { imageFilesErrors.put( imageFiles.get( i ), "image does not exist!" ); } } if ( paint ) { // just write files if something has been painted storeTileImageToFileSystem( postfix, out ); createWorldFile( tile ); String frm = outputFormat; if ( "raw".equals( outputFormat ) ) { frm = "tif"; } storeEnvelope( Double.toString( minimumRes ), baseName + postfix + '.' + frm, tileEnv ); } } /** * creates an instance of a BufferedImage depending on requested * target format * * @return the new image */ private BufferedImage createOutputImage() { BufferedImage out = null; if ( "jpg".equals( outputFormat ) || "jpeg".equals( outputFormat ) || "bmp".equals( outputFormat ) ) { // for bmp, jpg, jpeg use 3 byte: out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_RGB ); } else if ( "tif".equals( outputFormat ) || "tiff".equals( outputFormat ) || "png".equals( outputFormat ) ) { // for tif, tiff and png use 4 byte: out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB ); } else { ComponentColorModel ccm; if ( bitDepth == 16 ) { ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false, BufferedImage.OPAQUE, DataBuffer.TYPE_USHORT ); WritableRaster wr = ccm.createCompatibleWritableRaster( (int) pxWidthTile, (int) pxHeightTile ); out = new BufferedImage( ccm, wr, false, new Hashtable() ); } else { out = new BufferedImage( (int) pxWidthTile, (int) pxHeightTile, BufferedImage.TYPE_INT_ARGB ); } } return out; } /** * * @param postfix tile name postfix ( -> tile index $x_$y ) * @param out tile image to save */ private void storeTileImageToFileSystem( String postfix, BufferedImage out ) { try { String frm = outputFormat; if ( "raw".equals( frm ) ) { frm = "tif"; } String imageFile = outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + '.' + frm; File file = new File( imageFile ).getAbsoluteFile(); ImageUtils.saveImage( out, file, quality ); } catch ( IOException e ) { e.printStackTrace(); } } /** * draws an image map to the target tile considering defined interpolation * method for rescaling * * @param out target image tile * @param image source image map * @param tile tile description * @param mapEnv bbox of source image map * @param wf world fiel description of target image tile * @throws Exception */ private void drawImage( BufferedImage out,final BufferedImage image, Tile tile, WorldFile wf ) { Envelope tileEnv = tile.getTileEnvelope(); Envelope mapEnv = wf.getEnvelope(); GeoTransform gt2 = new WorldToScreenTransform( mapEnv.getMin().getX(), mapEnv.getMin().getY(), mapEnv.getMax().getX(), mapEnv.getMax().getY(), 0, 0, image.getWidth() - 1, image.getHeight() - 1 ); Envelope inter = mapEnv.createIntersection( tileEnv ); int x1 = (int) Math.round( gt2.getDestX( inter.getMin().getX() ) ); int y1 = (int) Math.round( gt2.getDestY( inter.getMax().getY() ) ); int x2 = (int) Math.round( gt2.getDestX( inter.getMax().getX() ) ); int y2 = (int) Math.round( gt2.getDestY( inter.getMin().getY() ) ); BufferedImage newImg = null; if ( x2 - x1 + 1 > 1 && y2 - y1 + 1 > 1 ) { BufferedImage img = image.getSubimage( x1, y1, x2 - x1 + 1, y2 - y1 + 1 ); // copy source image to a 4 Byte BufferedImage because there are // problems with handling 8 Bit palette images if ( img.getColorModel().getPixelSize() == 8 ) { LOG.logInfo( "copy 8Bit image to 32Bit image" ); BufferedImage bi = new BufferedImage( img.getWidth(), img.getHeight(), BufferedImage.TYPE_INT_ARGB ); Graphics g = bi.getGraphics(); try { g.drawImage( img, 0, 0, null ); } catch ( Exception e1 ) { System.out.println( e1.getMessage() ); } g.dispose(); } if ( wf.getResx() / minimumRes != 1d || wf.getResy() / minimumRes != 1d ) { ParameterBlock pb = new ParameterBlock(); pb.addSource( img ); pb.add( (float) ( wf.getResx() / minimumRes ) ); // The xScale pb.add( (float) ( wf.getResy() / minimumRes ) ); // The yScale pb.add( 0.0F ); // The x translation pb.add( 0.0F ); // The y translation pb.add( interpolation ); // The interpolation // Create the scale operation RenderedOp ro = JAI.create( "scale", pb, null ); try { newImg = ro.getAsBufferedImage(); } catch ( Exception e ) { System.out.println( image.getWidth() + " " + image.getHeight() ); System.out.println( "ee " + x1 + " " + y1 + " " + ( x2 - x1 + 1 ) + " " + ( y2 - y1 + 1 ) ); System.out.println( e.getMessage() ); imageFilesErrors.put( tile.getPostfix(), StringTools.stackTraceToString( e ) ); } } else { newImg = img; } } GeoTransform gt = new WorldToScreenTransform( tileEnv.getMin().getX(), tileEnv.getMin().getY(), tileEnv.getMax().getX(), tileEnv.getMax().getY(), 0, 0, out.getWidth() - 1, out.getHeight() - 1 ); x1 = (int) Math.round( gt.getDestX( inter.getMin().getX() ) ); y1 = (int) Math.round( gt.getDestY( inter.getMax().getY() ) ); x2 = (int) Math.round( gt.getDestX( inter.getMax().getX() ) ); y2 = (int) Math.round( gt.getDestY( inter.getMin().getY() ) ); if ( x2 - x1 > 1 && y2 - y1 > 1 ) { // ensure that there is something to draw try { for ( int i = 0; i < newImg.getWidth(); i++ ) { for ( int j = 0; j < newImg.getHeight(); j++ ) { if ( x1 + i < out.getWidth() && y1 + j < out.getHeight() ) { out.setRGB( x1 + i, y1 + j, newImg.getRGB( i, j ) ); } } } } catch ( Exception e ) { e.printStackTrace(); System.out.println( newImg.getWidth() + " " + newImg.getHeight() ); System.out.println( "ww " + x1 + " " + y1 + " " + ( x2 - x1 + 1 ) + " " + ( y2 - y1 + 1 ) ); System.out.println( e.getMessage() ); imageFilesErrors.put( tile.getPostfix(), StringTools.stackTraceToString( e ) ); } } } /** * creates an interpolation object from a well known name * @param interpolation * @return * @throws Exception */ private Interpolation createInterpolation( String interpolation ) { Interpolation interpol = null; if ( interpolation.equalsIgnoreCase( "Nearest Neighbor" ) ) { interpol = new InterpolationNearest(); } else if ( interpolation.equalsIgnoreCase( "Bicubic" ) ) { interpol = new InterpolationBicubic( 5 ); } else if ( interpolation.equalsIgnoreCase( "Bicubic2" ) ) { interpol = new InterpolationBicubic2( 5 ); } else if ( interpolation.equalsIgnoreCase( "Bilinear" ) ) { interpol = new InterpolationBilinear(); } else { throw new RuntimeException( "invalid interpolation method: " + interpolation ); } return interpol; } /** * Creates a world file for the corresponding tile in the <code>outputDir</code>. The name of * the output file is defined by the <code>baseName</code> and the tile's index of row and * column. * * @param tile The tile for which to create a world file. * @throws IOException */ private void createWorldFile( Tile tile ) throws IOException { Envelope env = tile.getTileEnvelope(); String postfix = tile.getPostfix(); StringBuffer sb = new StringBuffer( 1000 ); sb.append( minimumRes ).append( "\n" ).append( 0.0 ).append( "\n" ).append( 0.0 ); sb.append( "\n" ).append( ( -1 ) * minimumRes ).append( "\n" ); sb.append( env.getMin().getX() ).append( "\n" ).append( env.getMax().getY() ); sb.append( "\n" ); File f = new File( outputDir + '/' + Double.toString( minimumRes ) + '/' + baseName + postfix + ".wld" ); FileWriter fw = new FileWriter( f ); PrintWriter pw = new PrintWriter( fw ); pw.print( sb.toString() ); pw.close(); fw.close(); } /** * stores an envelope and the assigend image file information into a * feature/featureCollection * * @param dir directory where the image file is stored * @param file name of the image file * @param env bbox of the image file */ private void storeEnvelope( String dir, String file, Envelope env ) { try { Geometry geom = GeometryFactory.createSurface( env, null ); FeatureProperty[] props = new FeatureProperty[3]; props[0] = FeatureFactory.createFeatureProperty( "GEOM", geom ); props[1] = FeatureFactory.createFeatureProperty( GridCoverageExchangeIm.SHAPE_IMAGE_FILENAME, file ); props[2] = FeatureFactory.createFeatureProperty( GridCoverageExchangeIm.SHAPE_DIR_NAME, dir ); Feature feat = FeatureFactory.createFeature( "file", ftype, props ); fc.add( feat ); } catch ( Exception e ) { e.printStackTrace(); } } /** * creates a configuration file (extended CoverageDescriotion) for a WCS coverage * considering the passed resolution levels * * @param targetResolutions */ private void createConfigurationFile( double[] targetResolutions ) { // copy this file to the target directory String resolutions = ""; java.util.Arrays.sort( targetResolutions ); int length = targetResolutions.length; for ( int i = 0; i < length; i++ ) { resolutions += String.valueOf( targetResolutions[length - 1 - i] ); if ( i < ( length - 1 ) ) resolutions += ','; } try { Map<String, String> param = new HashMap<String, String>( 20 ); Envelope llEnv = getLatLonEnvelope( combiningEnvelope ); param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ',' + String.valueOf( llEnv.getMin().getY() ) ); param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ',' + String.valueOf( llEnv.getMax().getY() ) ); param.put( "upperleft", String.valueOf( combiningEnvelope.getMin().getX() ) + ',' + String.valueOf( combiningEnvelope.getMin().getY() ) ); param.put( "lowerright", String.valueOf( combiningEnvelope.getMax().getX() ) + ',' + combiningEnvelope.getMax().getY() ); File dir = new File( outputDir ); if ( dir.isAbsolute() ) { param.put( "dataDir", outputDir + '/' ); } else { param.put( "dataDir", "" ); } param.put( "label", baseName ); param.put( "name", baseName ); param.put( "description", "" ); param.put( "keywords", "" ); param.put( "resolutions", resolutions ); String frm = outputFormat; if ( "raw".equals( outputFormat ) ) { frm = "tif"; } param.put( "mimeType", frm ); String[] t = StringTools.toArray( srs, ":", false ); param.put( "srs", t[t.length-1] ); Reader reader = new InputStreamReader( configURL.openStream() ); XSLTDocument xslt = new XSLTDocument(); xslt.load( configXSL ); XMLFragment xml = xslt.transform( reader, XMLFragment.DEFAULT_URL, null, param ); reader.close(); // write the result String dstFilename = "wcs_" + baseName + "_configuration.xml"; File dstFile = new File( outputDir, dstFilename ); String configurationFilename = dstFile.getAbsolutePath().toString(); FileOutputStream fos = new FileOutputStream( configurationFilename ); xml.write( fos ); fos.close(); } catch ( Exception e1 ) { e1.printStackTrace(); } } private Envelope getLatLonEnvelope( Envelope env ) throws Exception { IGeoTransformer gt = new GeoTransformer( "EPSG:4326" ); return gt.transform( env, srs ); } /** * */ private void updateCapabilitiesFile( File capabilitiesFile ) { try { XSLTDocument xslt = new XSLTDocument(); xslt.load( inputXSL ); Map<String, String> param = new HashMap<String, String>(); param.put( "dataDirectory", outputDir ); String url = new File( "wcs_" + baseName + "_configuration.xml" ).toURL().toString(); param.put( "configFile", url ); Envelope llEnv = getLatLonEnvelope( combiningEnvelope ); param.put( "upperleftll", String.valueOf( llEnv.getMin().getX() ) + ',' + String.valueOf( llEnv.getMin().getY() ) ); param.put( "lowerrightll", String.valueOf( llEnv.getMax().getX() ) + ',' + String.valueOf( llEnv.getMax().getY() ) ); param.put( "name", baseName ); param.put( "label", baseName ); param.put( "description", "" ); param.put( "keywords", "" ); XMLFragment xml = new XMLFragment(); xml.load( capabilitiesFile.toURL() ); xml = xslt.transform( xml, capabilitiesFile.toURL().toExternalForm(), null, param ); // write the result FileOutputStream fos = new FileOutputStream( capabilitiesFile ); xml.write( fos ); fos.close(); } catch ( Exception e ) { e.printStackTrace(); } } /** * Validates the content of <code>map</code>, to see, if necessary arguments were passed * when calling this class. * * @param map * @throws Exception */ private static void validate( Properties map ) throws Exception { if ( map.get( "-outDir" ) == null ) { throw new Exception( "-outDir must be set" ); } String s = (String) map.get( "-outDir" ); if ( s.endsWith( "/" ) || s.endsWith( "\\" ) ) { s = s.substring( 0, s.length() - 1 ); } if ( map.get( "-baseName" ) == null ) { throw new Exception( "-baseName must be set" ); } if ( map.get( "-outputFormat" ) == null ) { map.put( "-outputFormat", "png" ); } else { String format = ( (String) map.get( "-outputFormat" ) ).toLowerCase(); if ( !"bmp".equals( format ) && !"png".equals( format ) && !"jpg".equals( format ) && !"jpeg".equals( format ) && !"tif".equals( format ) && !"tiff".equals( format ) && !( "raw" ).equals( format ) ) { throw new Exception( "-outputFormat must be one of the following: " + "'bmp', 'jpeg', 'jpg', 'png', 'tif', 'tiff', 'raw'." ); } } if ( map.get( "-maxTileSize" ) == null ) { map.put( "-maxTileSize", "500" ); } if ( map.get( "-srs" ) == null ) { map.put( "-srs", "EPSG:4326" ); } if ( map.get( "-interpolation" ) == null ) { map.put( "-interpolation", "Nearest Neighbor" ); } if ( map.get( "-noOfLevel" ) == null ) { map.put( "-noOfLevel", "1" ); } if ( map.get( "-worldFileType" ) == null ) { map.put( "-worldFileType", "center" ); } if ( map.get( "-quality" ) == null ) { map.put( "-quality", "0.95" ); } if ( map.get( "-cacheSize" ) == null ) { map.put( "-cacheSize", "5" ); } if ( map.get( "-bbox" ) != null ) { double[] d = StringTools.toArrayDouble( (String) map.get( "-bbox" ), "," ); Envelope env = GeometryFactory.createEnvelope( d[0], d[1], d[2], d[3], null ); map.put( "-bbox", env ); if ( map.get( "-resolution" ) == null ) { throw new Exception( "-resolution must be set if -bbox is set" ); } map.put( "-resolution", new Double( (String) map.get( "-resolution" ) ) ); } else { map.put( "-resolution", new Double( -1 ) ); } } /** * returns the list of image map files to consider read from -mapFiles * parameter * * @param mapFiles * @return */ private static List getFileList( String[] mapFiles ) { List imageFiles = new ArrayList(); for ( int i = 0; i < mapFiles.length; i++ ) { imageFiles.add( mapFiles[i] ); } return imageFiles; } /** * returns the list of image map files to consider read from a defined * root directory. * * @param rootDir root directory where to read image map files * @param subdirs true if subdirectories of the root directory shall be parsed * for image maps too * @return */ private static List getFileList( String rootDir, boolean subdirs ) { List list = new ArrayList( 10000 ); File file = new File( rootDir ); String[] entries = file.list( new DFileFilter() ); for ( int i = 0; i < entries.length; i++ ) { File entry = new File( rootDir + '/' + entries[i] ); if ( entry.isDirectory() && subdirs ) { list = readSubDirs( entry, list ); } else { list.add( rootDir + '/' + entries[i] ); } } return list; } /** * * @param file * @param list * @return */ private static List readSubDirs( File file, List list ) { String[] entries = file.list( new DFileFilter() ); for ( int i = 0; i < entries.length; i++ ) { File entry = new File( file.getAbsolutePath() + '/' + entries[i] ); if ( entry.isDirectory() ) { list = readSubDirs( entry, list ); } else { list.add( file.getAbsolutePath() + '/' + entries[i] ); } } return list; } /** * returns the list of image map files to consider read from a dbase file * defined by the dbase parameter * * @param dbaseFile name of the dbase file * @param fileColumn name of the column containing the image map files names * @param baseDir name of the directory where the image map files are stored * if this parameter is <code>null</code> it is assumed that * the image map files are full referenced within the dbase * @param sort true if map image file names shall be sorted * @param sortColum name of the column that shall be used for sorting * @return */ private static List getFileList( String dBaseFile, String fileColumn, String baseDir, boolean sort, String sortColum, String sortDirection ) throws Exception { //handle dbase file extension and file location/reading problems if ( dBaseFile.endsWith( ".dbf" ) ) { dBaseFile = dBaseFile.substring( 0, dBaseFile.lastIndexOf( "." ) ); } DBaseFile dbf = new DBaseFile( dBaseFile ); // sort dbase file contents chronologicaly (oldest first) int cnt = dbf.getRecordNum(); Object[][] mapItems = new Object[cnt][2]; QualifiedName fileC = new QualifiedName( APP_PREFIX, fileColumn.toUpperCase(), DEEGREEAPP ); QualifiedName sortC = null; if ( sort ) { sortC = new QualifiedName( APP_PREFIX, sortColum.toUpperCase(), DEEGREEAPP ); } for ( int i = 0; i < cnt; i++ ) { if ( sort ) { mapItems[i][0] = dbf.getFRow( i + 1 ).getDefaultProperty( sortC ).getValue(); } else { mapItems[i][0] = new Integer( 1 ); } // name of map file mapItems[i][1] = dbf.getFRow( i + 1 ).getDefaultProperty( fileC ).getValue(); } Arrays.sort( mapItems, new MapAgeComparator( sortDirection ) ); // extract names of image files from dBase file and attach them to rootDir if ( baseDir == null ) { baseDir = ""; } else if ( !baseDir.endsWith( "/" ) && !baseDir.endsWith( "\\" ) ) { baseDir = baseDir + "/"; } List imageFiles = new ArrayList( mapItems.length ); for ( int i = 0; i < mapItems.length; i++ ) { if ( mapItems[i][0] != null ) { LOG.logDebug( "" + mapItems[i][0] ); imageFiles.add( baseDir + mapItems[i][1] ); } } return imageFiles; } private static void printHelp() { System.out.println( "-outDir directory where resulting tiles and describing shape(s) will be stored (mandatory)\r\n" + "-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" + "-outputFormat name of the image format used for created tiles (png|jpg|jpeg|bmp|tif|tiff|gif|raw default png)\r\n" + "-maxTileSize maximum size of created raster tiles in pixel (default 500)\r\n" + "-srs name of the spatial reference system used for the coverage (default EPSG:4326)\r\n" + "-interpolation interpolation method used for rescaling raster images (Nearest Neighbor|Bicubic|Bicubic2|Bilinear default Nearest Neighbor)\r\n" + " be careful using Bicubic and Bicubic2 interpolation; there seems to be a problem with JAI\r\n" + " If you use the proogram with images (tif) containing raw data like DEMs just use \r\n" + " Nearest Neighbor interpolation. All other interpolation methods will cause artefacts." + "-bbox boundingbox of the the resulting coverage. If not set the bbox will be determined by analysing the input map files. (optional)\r\n" + "-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" + "-noOfLevel number of tree levels created (optional default = 1)\r\n" + "-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" + "-h or -? print this help\r\n" + "\r\n" + "Input files\r\n" + "there are three alternative ways/parameters to define which input files shall be used for creating a raster tree:\r\n" + "1)\r\n" + "-mapFiles defines a list of image file names (including full path information) seperated by \',\', \';\' or \'|\'\r\n" + "\r\n" + "2)\r\n" + "-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" + "-subDirs conditional parameter used with -rootDir. It defines if all sub directories of -rootDir shall be parsed too (true|false default false)\r\n" + "\r\n" + "3)\r\n" + "-dbaseFile name a dBase file that contains a column listing all files to be considered by the program\r\n" + "-fileColumn name of the column containing the file names (mandatory if -dbaseFile is defined)\r\n" + "-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" + "-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" + "-sortDirection If -sortColumn is defined this parameter will be used for definition of sorting direction (UP|DOWN default UP)\r\n" + "-worldFileType two types of are common: \r\n " + " a) the boundingbox is defined on the center of the corner pixels; \r\n " + " b) the boundingbox is defined on the outter corner of the corner pixels; \r\n " + " first is default and will be used if this parameter is not set; second will be use if '-worldFileType outter' is defined.\r\n" + "-quality image quality if jpeg is used as output format; valid range is from 0..1 (default 0.95) \r\n" + "-bitDepth image bit depth; valid values are 32 and 16, default is 16 \r\n" + "-cacheSize number of images that shall be cached. A larger value (~20) increases speed but also amount of required memory. (default = 5)\r\n" + "-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" + " 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" + "\r\n" + "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." ); System.out.println(); System.out.println( "Example (windows):" ); System.out.println( "rem set environment variables to enable coordinate system transformation" ); System.out.println( "set PATH=%PATH%;D:\\deegree\\lib\\proj4\\win32" ); System.out.println( "set PROJ_LIB=D:\\java\\source\\deegree2\\lib\\proj4" ); System.out.println( "java -Xms300m -Xmx1000m -classpath .;./classes;./lib/jai_codec.jar;" + "./lib/jai_core.jar;./lib/mlibwrapper_jai.jar;./lib/jts-1.6.jar;" + "./lib/jaxen-1.1-beta-7.jar org.deegree.tools.raster.RasterTreeBuilder " + "-dbaseFile D:/lgv/resources/data/dbase/dip.dbf -outDir D:/lgv/output/ " + "-baseName out -outputFormat jpg -maxTileSize 500 -noOfLevel 4 -interpolation " + "Bilinear -bbox 3542428,5918168,3593354,5957043 -resolution 0.2 -sortColumn " + "PLANJAHR -fileColumn NAME_PNG -sortDirection UP -quality 0.91 -baseDir " + "D:/lgv/resources/data/images/ -cacheSize 10" ); } /** * * @param args Example arguments to pass when calling are: * <ul> * <li>-mapFiles D:/temp/europe_DK.jpg,D:/temp/europe_BeNeLux.jpg</li> * <li>-outDir D:/temp/out/</li> * <li>-baseName pretty</li> * <li>-outputFormat png</li> * <li>-maxTileSize 600</li> * </ul> * * @throws Exception */ public static void main( String[] args ) throws Exception { Properties map = new Properties(); for ( int i = 0; i < args.length; i += 2 ) { map.put( args[i], args[i + 1] ); } if ( map.get( "-?" ) != null || map.get( "-h" ) != null ) { printHelp(); return; } try { validate( map ); } catch ( Exception e ) { LOG.logInfo( map.toString() ); System.out.println( e.getMessage() ); System.out.println(); printHelp(); return; } // read input parameters String outDir = map.getProperty( "-outDir" ); String baseName = map.getProperty( "-baseName" ); String outputFormat = map.getProperty( "-outputFormat" ); String srs = map.getProperty( "-srs" ); String interpolation = map.getProperty( "-interpolation" ); Envelope env = (Envelope) map.get( "-bbox" ); double resolution = ( (Double) map.get( "-resolution" ) ).doubleValue(); int level = Integer.parseInt( map.getProperty( "-noOfLevel" ) ); double maxTileSize = ( Double.valueOf( map.getProperty( "-maxTileSize" ) ) ).doubleValue(); String worldFileType = map.getProperty( "-worldFileType" ); float quality = Float.parseFloat( map.getProperty( "-quality" ) ); int cacheSize = Integer.parseInt( map.getProperty( "-cacheSize" ) ); String backgroundColor = map.getProperty( "-bgColor" ); int depth = 0; if ( map.get( "-bitDepth" ) != null ) { depth = Integer.parseInt( map.get( "-bitDepth" ).toString() ); } List imageFiles = null; if ( map.get( "-mapFiles" ) != null ) { String[] mapFiles = StringTools.toArray( map.getProperty( "-mapFiles" ), ",;|", true ); imageFiles = getFileList( mapFiles ); } else if ( map.get( "-dbaseFile" ) != null ) { String dBaseFile = map.getProperty( "-dbaseFile" ); String fileColum = map.getProperty( "-fileColumn" ); String baseDir = map.getProperty( "-baseDir" ); if ( baseDir == null ) { baseDir = map.getProperty( "-rootDir" ); } boolean sort = map.get( "-sortColumn" ) != null; String sortColumn = map.getProperty( "-sortColumn" ); if ( map.get( "-sortDirection" ) == null ) { map.put( "-sortDirection", "UP" ); } String sortDirection = map.getProperty( "-sortDirection" ); imageFiles = getFileList( dBaseFile, fileColum, baseDir, sort, sortColumn, sortDirection ); } else if ( map.get( "-rootDir" ) != null ) { String rootDir = map.getProperty( "-rootDir" ); boolean subDirs = "true".equals( map.get( "-subDirs" ) ); imageFiles = getFileList( rootDir, subDirs ); } else { LOG.logInfo( map.toString() ); System.out.println( "-mapFiles, -rootDir or -dbaseFile parameter must be defined" ); printHelp(); return; } LOG.logDebug( imageFiles.toString() ); LOG.logInfo( map.toString() ); // initialize RasterTreeBuilder RasterTreeBuilder rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs, interpolation, worldFileType, quality, cacheSize, backgroundColor, depth ); // calculate bbox and resolution from input images if parameters are not set if ( env == null ) { WorldFile wf = rtb.determineCombiningBBox(); env = wf.getEnvelope(); resolution = wf.getResx(); } // Calculate necessary number of levels to get not more than 4 // tiles in highest resolution if ( level == -1 ) { rtb.init( env, resolution ); level = 0; int numTilesMax = Math.min( rtb.tileCols, rtb.tileRows ); int numTiles = 4; while ( numTiles < numTilesMax ) { level += 1; numTiles *= 2; } } if ( level == 0 ) level = 1; System.out.println( "Number of Levels: " + level ); // create tree where for each loop resolution will be halfed double[] re = new double[level]; for ( int i = 0; i < level; i++ ) { rtb.init( env, resolution ); rtb.start(); rtb.logCollectedErrors(); re[i] = resolution; if ( i < level - 1 ) { String dir = outDir + "/" + Double.toString( resolution ); imageFiles = getFileList( dir, false ); rtb = new RasterTreeBuilder( imageFiles, outDir, baseName, outputFormat, maxTileSize, srs, interpolation, "outter", quality, cacheSize, backgroundColor, depth ); resolution = resolution * 2; } } LOG.logInfo( "create configuration files ..." ); rtb.createConfigurationFile( re ); if ( map.get( "-capabilitiesFile" ) != null ) { LOG.logInfo( "adjust capabilities ..." ); File file = new File( map.getProperty( "-capabilitiesFile" ) ); rtb.updateCapabilitiesFile( file ); } rtb.logCollectedErrors(); } /** * simple class for caching images * */ class ImageCache { private int size = 3; private int k = 1; private Object[][] cache; public ImageCache( int size ) { this.size = size; cache = new Object[size][2]; } public BufferedImage getImage( String name ) { for ( int i = 0; i < cache.length; i++ ) { if ( name.equals( cache[i][0] ) ) { return (BufferedImage) cache[i][1]; } } return null; } public void putImage( String name, BufferedImage image ) { if ( getImage( name ) == null ) { cache[k % size][0] = name; cache[k % size][1] = image; k++; } } } /** * class: official version of a FilenameFilter */ static class DFileFilter implements FilenameFilter { private List extensions = null; public DFileFilter() { extensions = new ArrayList(); extensions.add( "JPEG" ); extensions.add( "JPG" ); extensions.add( "BMP" ); extensions.add( "PNG" ); extensions.add( "GIF" ); extensions.add( "TIF" ); extensions.add( "TIFF" ); extensions.add( "GEOTIFF" ); } public String getDescription() { return "*.*"; } /* * (non-Javadoc) * * @see java.io.FilenameFilter#accept(java.io.File, java.lang.String) */ public boolean accept( java.io.File file, String name ) { int pos = name.lastIndexOf( "." ); String ext = name.substring( pos + 1 ).toUpperCase(); if ( file.isDirectory() ) { String s = file.getAbsolutePath() + '/' + name; File tmp = new File( s ); if ( tmp.isDirectory() ) { return true; } } return extensions.contains( ext ); } } /** * * This class enables sorting of dBaseFile objects in chronological order * (lowest first, highest last). * * @author <a href="mailto:mays@lat-lon.de">Judit Mays</a> * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> * @author last edited by: $Author$ * * @version 2.0, $Revision$, $Date$ * * @since 2.0 */ private static class MapAgeComparator implements Comparator { private String direction = null; public MapAgeComparator( String direction ) { this.direction = direction.toUpperCase(); } public int compare( Object o1, Object o2 ) { Object[] o1a = (Object[]) o1; Object[] o2a = (Object[]) o2; if ( o1a[0] == null || o2a[0] == null ) { return 0; } if ( direction.equals( "UP" ) ) { return o1a[0].toString().compareTo( o2a[0].toString() ); } return o2a[0].toString().compareTo( o1a[0].toString() ); } } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log$ Revision 1.52 2006/12/03 21:21:47 poth bug fix - setting CRS to created WCS configuration Revision 1.51 2006/11/27 09:07:53 poth JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code. Revision 1.50 2006/11/18 11:08:52 poth *** empty log message *** Revision 1.49 2006/11/17 08:59:26 poth bug fix Revision 1.48 2006/11/16 10:33:40 poth useless code removed Revision 1.47 2006/11/16 10:02:29 poth bug fix: g.drawImage( img, ... ) does not work as expected if 'img' contains raw data ... Revision 1.46 2006/11/15 16:30:40 schmitz Using BufferedImage.TYPE_INT_ARGB now, and returned to Graphics.drawImage copying. Revision 1.45 2006/11/15 11:22:02 schmitz Data is now copied by hand using the databuffers. Revision 1.44 2006/11/13 10:23:16 poth support for automatic detection of useful pyramid levels Revision 1.43 2006/11/08 16:47:47 schmitz Changed formatting, removed imports. Revision 1.42 2006/11/08 16:12:29 schmitz Fixed the usage of ushort databuffers. Revision 1.41 2006/11/08 10:52:46 poth a few simplifications reading source images Revision 1.40 2006/11/06 20:48:19 poth support for raw data (32 BIT Tiff without color map -> DEM) added Revision 1.39 2006/09/27 16:46:41 poth transformation method signature changed Revision 1.38 2006/09/07 07:10:20 poth bug fix reading sub directories Revision 1.37 2006/08/30 16:59:13 mschneider Moved definitions of DEEGREEAPP and APP_PREFIX here (this are *not* constant bindings). Revision 1.36 2006/08/29 19:54:14 poth footer corrected Revision 1.35 2006/05/25 20:02:13 poth support for using relative pathes for -outDir parameter added Revision 1.34 2006/05/18 07:52:35 poth bug fix for tiles sorted by a dbase file Revision 1.33 2006/05/12 15:26:05 poth *** empty log message *** Revision 1.32 2006/05/04 08:31:29 poth *** empty log message *** Revision 1.31 2006/05/03 20:09:52 poth *** empty log message *** Revision 1.30 2006/05/03 13:00:06 poth *** empty log message *** Revision 1.29 2006/05/03 09:02:43 poth *** empty log message *** Revision 1.28 2006/05/01 20:15:26 poth *** empty log message *** Revision 1.27 2006/04/15 15:30:20 poth *** empty log message *** Revision 1.26 2006/04/06 20:25:26 poth *** empty log message *** Revision 1.25 2006/03/31 13:31:28 poth *** empty log message *** Revision 1.24 2006/03/31 13:22:09 poth *** empty log message *** Revision 1.23 2006/03/30 21:20:26 poth *** empty log message *** Revision 1.22 2006/03/15 22:20:09 poth *** empty log message *** Revision 1.21 2006/03/14 08:06:14 poth *** empty log message *** Revision 1.20 2006/03/06 12:40:45 poth *** empty log message *** Revision 1.19 2006/02/23 07:45:24 poth *** empty log message *** Revision 1.18 2006/02/20 09:27:26 poth *** empty log message *** Revision 1.17 2006/02/09 11:30:07 poth *** empty log message *** Revision 1.16 2006/02/09 11:29:09 poth *** empty log message *** Revision 1.15 2006/02/07 13:40:48 poth *** empty log message *** Revision 1.14 2006/02/06 15:34:50 poth *** empty log message *** Revision 1.13 2006/02/06 13:38:48 ncho SN AP changed Revision 1.12 2006/02/04 14:36:20 poth *** empty log message *** Revision 1.11 2006/02/03 07:53:37 poth *** empty log message *** Revision 1.10 2006/01/31 19:19:05 poth *** empty log message *** Revision 1.9 2006/01/31 19:16:34 poth *** empty log message *** Revision 1.5 2006/01/31 13:06:16 poth *** empty log message *** Revision 1.3 2006/01/30 09:39:55 poth *** empty log message *** Revision 1.2 2006/01/30 08:52:48 poth *** empty log message *** Revision 1.1 2006/01/29 20:59:08 poth *** empty log message *** Revision 1.11 2006/01/27 15:44:46 poth *** empty log message *** Revision 1.10 2006/01/20 19:45:53 poth *** empty log message *** Revision 1.9 2006/01/13 08:48:42 poth *** empty log message *** Revision 1.8 2006/01/12 15:27:26 mays create world file for each tile and create empty image files for empty tiles Revision 1.7 2006/01/09 09:18:39 mays validate outputFormat Revision 1.6 2006/01/06 18:06:28 mays make use of args passed to main method; and minor restructuring Revision 1.5 2006/01/06 15:18:19 poth *** empty log message *** Revision 1.4 2006/01/06 14:24:35 mays major restructuring because of buffer size problems. now, images and tiles are only read into buffer, when realy needed. Revision 1.3 2006/01/06 10:35:53 mays add methods to create tiles and images; major restructuring and minor cleanup Revision 1.2 2006/01/05 11:37:55 mays add methods to create tiles images and tiles bboxes; minor cleanup Revision 1.1 2006/01/04 17:17:26 mays first implementation of class - not finished yet ********************************************************************** */