/*---------------- FILE HEADER ------------------------------------------
This file is part of deegree.
Copyright (C) 2001-2006 by:
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
Aennchenstr. 19
53115 Bonn
Germany
E-Mail: poth@lat-lon.de
Klaus Greve
Department of Geography
University of Bonn
Meckenheimer Allee 166
53115 Bonn
Germany
E-Mail: klaus.greve@uni-bonn.de
---------------------------------------------------------------------------*/
package org.deegree.tools.raster;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.image.BandedSampleModel;
import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferShort;
import java.awt.image.Raster;
import java.awt.image.SampleModel;
import java.awt.image.renderable.ParameterBlock;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.net.URL;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.StringTokenizer;
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 javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
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.CharsetUtils;
import org.deegree.framework.xml.XMLFragment;
import org.deegree.framework.xml.XSLTDocument;
import org.deegree.graphics.Encoders;
import org.deegree.io.geotiff.GeoTiffKey;
import org.deegree.io.geotiff.GeoTiffTag;
import org.deegree.io.shpapi.ShapeFile;
import org.deegree.model.coverage.grid.GridCoverageExchangeIm;
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 com.sun.media.jai.codec.FileSeekableStream;
import com.sun.media.jai.codec.TIFFDirectory;
import com.sun.media.jai.codec.TIFFField;
/**
* @deprecated use @see org.deegree.tools.raster.RasterTreeBuilder instead; this class
* will be removed from deegree at the end of 2007
*
* @author Norman Barker
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
*/
public class AutoTiler {
private static final ILogger LOG = LoggerFactory.getLogger( AutoTiler.class );
private boolean geoTiff = false;
// main image
private RenderedOp rop = null;
private BufferedImage image = null;
private ProgressObserver progressObserver = null;
protected double[] targetResolutions = null;
private HashMap featColl = new HashMap();
private Interpolation interpolation = null;;
// main image resolution
private double resx = 0;
private double resy = 0;
private double xmax = 0;
// main image bounding box
private double xmin = 0;
private double ymax = 0;
private double ymin = 0;
private String crs = null;
// layerID and title of the layer
private String imageName = null;
private String targetDir = null;
private String targetFormat = null;
private String mimeType = null;
private Integer bitDepth = null;
private String name = null;
private String description = null;
private String keywords = null;
private String configurationFilename = null;
private float quality;
private int count = 0;
// global variable
public static double WCS_ORDINATE_LOW_X;
public static double WCS_ORDINATE_LOW_Y;
public static double WCS_ORDINATE_HIGH_X;
public static double WCS_ORDINATE_HIGH_Y;
public static double MIN_X_SPATIAL = -1;
public static double MIN_Y_SPATIAL = -1;
public static double MAX_X_SPATIAL;
public static double MAX_Y_SPATIAL;
public static double MIN_X_LL = -1;
public static double MIN_Y_LL = -1;
public static double MAX_X_LL = -90;
public static double MAX_Y_LL = -180;
public static String CAPABS_XSL = "updateCapabilities.xsl";
public static String CONFIG_TEMPLATE = "template_wcs_configuration.xml";
public static String CONFIG_XSL = "updateConfig.xsl";
/**
* @param inFile
* @param dir
* @param format
* @param targetRes
* @param startIndex
* @param quality
* @param crs
*/
public AutoTiler( String imageSource, String targetDir, String targetFormat,
double[] targetResolutions, float quality, String crs,
String interpolation ) throws Exception {
this.targetDir = targetDir;
this.interpolation = createInterpolation( interpolation );
File file = new File( targetDir );
if ( !file.exists() ) {
file.mkdir();
}
this.targetFormat = targetFormat.toLowerCase();
this.quality = quality;
setTargetResolutions( targetResolutions );
int pos = imageSource.lastIndexOf( '/' );
this.imageName = imageSource.substring( pos + 1, imageSource.length() );
this.crs = crs;
// load the main image
image = loadImage( imageSource );
// get the bounding box of the source image by evaluating its world
// file
if ( !geoTiff ) {
readWorldFile( imageSource );
}
}
private Interpolation createInterpolation( String interpolation )
throws Exception {
Interpolation interpol = null;
if ( interpolation == null )
interpolation = "Nearest Neighbor";
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 Exception( "invalid interpolation method: " + interpolation );
}
return interpol;
}
/**
* Gets the latitude and longitude coordinates (xmin, ymin, xmax and ymax) of the image.
*/
private void readWorldFile( String filename )
throws Exception {
try {
// Gets the substring beginning at the specified beginIndex (0) -
// the
// beginning index, inclusive - and extends to the character at
// index endIndex (position of '.') - the ending index, exclusive.
String fname = null;
int pos = filename.lastIndexOf( "." );
filename = filename.substring( 0, pos );
// Looks for corresponding worldfiles.
if ( ( new File( filename + ".tfw" ) ).exists() ) {
fname = filename + ".tfw";
} else if ( ( new File( filename + ".wld" ) ).exists() ) {
fname = filename + ".wld";
} else if ( ( new File( filename + ".jgw" ) ).exists() ) {
fname = filename + ".jgw";
} else if ( ( new File( filename + ".jpgw" ) ).exists() ) {
fname = filename + ".jpgw";
} else if ( ( new File( filename + ".gfw" ) ).exists() ) {
fname = filename + ".gfw";
} else if ( ( new File( filename + ".gifw" ) ).exists() ) {
fname = filename + ".gifw";
} else if ( ( new File( filename + ".pgw" ) ).exists() ) {
fname = filename + ".pgw";
} else if ( ( new File( filename + ".pngw" ) ).exists() ) {
fname = filename + ".pngw";
} else {
throw new Exception( "Not a world file for: " + filename );
}
// Reads character files.
// The constructors of this class (FileReader) assume that the
// default character
// encoding and the default byte-buffer size are appropriate.
// The BufferedReader reads text from a character-input stream,
// buffering characters so as
// to provide for the efficient reading of characters
BufferedReader br = new BufferedReader( new FileReader( fname ) );
String s = null;
int cnt = 0;
double d1 = 0;
double d2 = 0;
double d3 = 0;
double d4 = 0;
while ( ( s = br.readLine() ) != null ) {
cnt++;
s = s.trim();
switch ( cnt ) {
case 1:
d1 = Double.parseDouble( s );
break;
case 4:
d2 = Double.parseDouble( s );
break;
case 5:
d3 = Double.parseDouble( s );
break;
case 6:
d4 = Double.parseDouble( s );
break;
}
}
br.close();
double d5 = d3 + ( image.getWidth() * d1 );
double d6 = d4 + ( image.getHeight() * d2 );
ymax = d4;
ymin = d6;
xmax = d5;
xmin = d3;
resx = ( xmax - xmin ) / image.getWidth();
resy = ( ymax - ymin ) / image.getHeight();
} catch ( Exception ex ) {
ex.printStackTrace();
}
}
/**
* loads the base image
*/
private BufferedImage loadImage( String imageSource )
throws Exception {
LOG.logInfo( "reading source image ..." );
BufferedImage bi = null;
FileSeekableStream fss = new FileSeekableStream( imageSource );
rop = JAI.create( "stream", fss );
if ( imageSource.toUpperCase().endsWith( ".TIFF" )
|| imageSource.toUpperCase().endsWith( ".TIF" ) ) {
geoTiff = isGeoTIFFFormat( rop );
if ( geoTiff ) {
readBBoxFromGeoTIFF( rop );
if ( ( bitDepth != null ) && ( bitDepth.intValue() == 16 ) ) {
int width = rop.getWidth();
int height = rop.getHeight();
// read data as 'raw' information and not as image
Raster raster = rop.getData( new Rectangle( 0, 0, width, height ) );
// get the data
short[] o = (short[]) raster.getDataElements( 0, 0, width, height, null );
int bands = raster.getNumBands();
short[][] bb = new short[bands][];
for ( int i = 0; i < bands; i++ ) {
bb[i] = new short[raster.getWidth() * raster.getHeight()];
}
int c = 0;
int u = 0;
for ( int i = 0; i < raster.getWidth(); i++ ) {
for ( int j = 0; j < raster.getHeight(); j++ ) {
for ( int z = 0; z < bands; z++ ) {
bb[z][u] = o[c++];
}
u++;
}
}
// create a new image from the data and serialize it to a file
DataBuffer db = new DataBufferShort( bb, width * height );
SampleModel sm = new BandedSampleModel( DataBuffer.TYPE_SHORT, width, height,
bands );
raster = Raster.createWritableRaster( sm, db, null );
// from a theoretical point of view the usage of TYPE_USHORT_GRAY
// doesn't seem to be very good idea but it works and I didn't found a
// way to create a RenderedImage with type Short
bi = new BufferedImage( width, height, BufferedImage.TYPE_USHORT_GRAY );
bi.setData( raster );
}
}
}
if ( bi == null ) {
bi = rop.getAsBufferedImage();
}
fss.close();
LOG.logInfo( "finished" );
return bi;
}
/**
* description: 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 void readBBoxFromGeoTIFF( RenderedOp rop )
throws Exception {
TIFFDirectory tifDir = (TIFFDirectory) rop.getDynamicProperty( "tiff_directory" );
TIFFField modelPixelScaleTag = tifDir.getField( GeoTiffTag.ModelPixelScaleTag );
resx = modelPixelScaleTag.getAsDouble( 0 );
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 );
if ( ( resx == 0.0 || resy == 0.0 )
|| ( val1 == 0.0 && val2 == 0.0 && val4 == 0.0 && val5 == 0.0 ) ) {
throw new Exception( "The image/coverage hasn't a 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;
LOG.logInfo( "resx: " + resx );
LOG.logInfo( "resy: " + resy );
LOG.logInfo( "origin x: " + xOrigin );
LOG.logInfo( "origin y: " + yOrigin );
xmin = xOrigin;
ymin = yBottom;
xmax = xRight;
ymax = yOrigin;
LOG.logInfo( "bbox: " + xmin + " " + ymin + " " + xmax + " " + ymax );
}
/**
* description: 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 )
this.bitDepth = new Integer( 16 );
}
// 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 );
}
int[] content = new int[3];
int key = -99;
if ( geoKeyDirectoryTag.containsKey( new Integer( GeoTiffKey.GTModelTypeGeoKey ) ) ) {
content = (int[]) geoKeyDirectoryTag.get( new Integer( GeoTiffKey.GTModelTypeGeoKey ) );
// TIFFTagLocation
if ( content[0] == 0 ) {
// return Value_Offset
key = content[2];
} else {
// TODO other TIFFTagLocation that GeoKeyDirectoryTag
}
} else {
LOG.logError( "Can't check EPSG codes, make sure it is ok!" );
}
int epsgNo = Integer.parseInt( crs.substring( crs.indexOf( ':' ) + 1, crs.length() ) );
if ( epsgNo != key && ( key != 1 ) ) {
// TODO seem to getting 1 here
/*
* LOG.logInfo( "Geotiff EPSG Number doesn't match input, overriding");
* epsgNo = key;
*/}
return true;
}
/**
* @param map
*
* @return
*/
private static boolean validate( HashMap map ) {
boolean valid = true;
if ( ( map.get( "-i" ) == null ) || ( map.get( "-o" ) == null )
|| ( map.get( "-f" ) == null ) || ( map.get( "-h" ) != null ) ) {
valid = false;
}
return valid;
}
public void setTargetResolutions( double[] targetRes ) {
if ( targetRes == null )
return;
// target resolutions have to be in descending order
java.util.Arrays.sort( targetRes );
this.targetResolutions = new double[targetRes.length];
for ( int j = ( targetRes.length - 1 ); j >= 0; j-- ) {
this.targetResolutions[targetRes.length - 1 - j] = targetRes[j];
}
setProgressObserver( new ProgressObserver() );
}
/**
* starts the creation of the tiles
*/
public void createTileImageTree()
throws Exception {
for ( int i = 0; i < targetResolutions.length; i++ ) {
FeatureCollection fc = FeatureFactory.createFeatureCollection( "fc"
+ targetResolutions[i],
1000 );
featColl.put( "fc" + targetResolutions[i], fc );
}
// need to calculate the lat lon bounding box
// Lat Lon WGS84 Geographic CRS
String targetCRS = "EPSG:4326";
double xminll = xmin;
double yminll = ymin;
double xmaxll = xmax;
double ymaxll = ymax;
if ( !crs.equalsIgnoreCase( targetCRS ) ) {
Envelope env = GeometryFactory.createEnvelope( xmin, ymin, xmax, ymax, null );
IGeoTransformer trans = new GeoTransformer(targetCRS);
try {
env = trans.transform( env, crs );
xminll = env.getMin().getX();
yminll = env.getMin().getY();
xmaxll = env.getMax().getX();
ymaxll = env.getMax().getY();
} catch ( Exception e ) {
LOG.logError( "Unsupported transformation, update lonLatEnvelope manually!" );
}
}
updateBounds( xminll, yminll, xmaxll, ymaxll, xmin, ymin, xmax, ymax );
LOG.logInfo( "creating tiles ..." );
tile( image, xmin, ymin, xmax, ymax, 0 );
for ( int i = 0; i < targetResolutions.length; i++ ) {
ShapeFile sh = new ShapeFile( targetDir + "/sh" + targetResolutions[i], "rw" );
sh.writeShape( (FeatureCollection) featColl.get( "fc" + targetResolutions[i] ) );
sh.close();
}
LOG.logInfo( "100%" );
LOG.logInfo( "finished" );
}
/**
* the method performes the creation of the tiles and the filling of the quadtree XML-document.
* the method will be call in a recursion for each defined level (scale).
*/
private void tile( BufferedImage img, double xmin, double ymin, double xmax, double ymax,
int res )
throws Exception {
// break condition
if ( res >= targetResolutions.length ) {
return;
}
BufferedImage im = null;
double xmin_ = 0;
double ymin_ = 0;
double xmax_ = 0;
double ymax_ = 0;
// calculate half of tile width and height to get tile (quarter)
// coordinates
double x2 = ( xmax - xmin ) / 2d;
double y2 = ( ymax - ymin ) / 2d;
// create the four quarters (tiles) for the submitted image and call
// this method
// in a recursion to create the next resolution level
for ( int i = 0; i < 4; i++ ) {
switch ( i ) {
case 0: {
// tile bounding box
xmin_ = xmin;
ymin_ = ymin;
xmax_ = xmin + x2;
ymax_ = ymin + y2;
im = img.getSubimage( 0, img.getHeight() / 2, img.getWidth() / 2,
img.getHeight() / 2 );
break;
}
case 1: {
// tile bounding box
xmin_ = xmin + x2;
ymin_ = ymin;
xmax_ = xmax;
ymax_ = ymin + y2;
im = img.getSubimage( img.getWidth() / 2, img.getHeight() / 2, img.getWidth() / 2,
img.getHeight() / 2 );
break;
}
case 2: {
// tile bounding box
xmin_ = xmin;
ymin_ = ymin + y2;
xmax_ = xmin + x2;
ymax_ = ymax;
im = img.getSubimage( 0, 0, img.getWidth() / 2, img.getHeight() / 2 );
break;
}
case 3: {
// tile bounding box
xmin_ = xmin + x2;
ymin_ = ymin + y2;
xmax_ = xmax;
ymax_ = ymax;
im = img.getSubimage( img.getWidth() / 2, 0, img.getWidth() / 2,
img.getHeight() / 2 );
break;
}
}
// calculate the tiles width and height for the current resolution
int tilex = (int) Math.round( ( xmax_ - xmin_ ) / targetResolutions[res] );
int tiley = (int) Math.round( ( ymax_ - ymin_ ) / targetResolutions[res] );
BufferedImage tmp = img.getSubimage( 0, 0, tilex, tiley );
ParameterBlock pb = new ParameterBlock();
pb.add( im );
// Create the AWTImage operation.
RenderedOp ro = JAI.create( "awtImage", pb );
pb = new ParameterBlock();
pb.addSource( ro );
pb.add( ( (float) tilex ) / im.getWidth() ); // The xScale
pb.add( ( (float) tiley ) / im.getHeight() ); // The yScale
pb.add( 0.0F ); // The x translation
pb.add( 0.0F ); // The y translation
pb.add( interpolation ); // The interpolation
pb.add( image );
// Create the scale operation
ro = JAI.create( "scale", pb, null );
tmp = new BufferedImage( img.getColorModel(),
tmp.getRaster().createCompatibleWritableRaster(), true,
new Hashtable() );
Graphics g = tmp.getGraphics();
g.drawImage( im, 0, 0, tilex, tiley, null );
g.dispose();
// observer output
progressObserver.write( new Integer( count ) );
// save tile to the filesystem
saveTile( targetDir + "/l" + targetResolutions[res], xmin_, ymin_, tmp );
createWorldFile( targetDir + "/l" + targetResolutions[res], xmin_, ymin_, xmax_, ymax_,
tilex, tiley );
//storeEnvelope( targetDir + "/l" + targetResolutions[res], targetResolutions[res],
// xmin_, ymin_, xmax_, ymax_ );
storeEnvelope( "l" + targetResolutions[res], targetResolutions[res], xmin_, ymin_,
xmax_, ymax_ );
// recursion !
tile( im, xmin_, ymin_, xmax_, ymax_, res + 1 );
}
}
/**
* @param string
* @param d
* @param xmin_
* @param ymin_
* @param xmax_
* @param ymax_
*/
private void storeEnvelope( String dir, double res, double xmin, double ymin, double xmax,
double ymax ) {
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 );
FeatureType ftype = FeatureFactory.createFeatureType( new QualifiedName( "tiles" ), false,
ftp );
try {
Envelope env = GeometryFactory.createEnvelope( xmin, ymin, xmax, ymax, null );
Geometry geom = GeometryFactory.createSurface( env, null );
FeatureProperty[] props = new FeatureProperty[3];
props[0] = FeatureFactory.createFeatureProperty( "GEOM", geom );
DecimalFormat fo = new DecimalFormat( "#.0" );
String sx = fo.format( xmin * 1000 );
sx = sx.substring( 0, sx.length() - 3 ) + "0";
String sy = fo.format( ymin * 1000 );
sy = sy.substring( 0, sy.length() - 3 ) + "0";
String file = "";
if ( targetFormat.equalsIgnoreCase( "geotiff" ) )
file = sx + "_" + sy + ".tif";
else
file = sx + "_" + sy + "." + targetFormat;
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 );
FeatureCollection fc = (FeatureCollection) featColl.get( "fc" + res );
fc.add( feat );
} catch ( Exception e ) {
e.printStackTrace();
}
}
/**
* @param string
* @param xmin_
* @param ymin_
* @param xmax_
* @param ymax_
* @param tilex
* @param tiley
*/
private void createWorldFile( String dir, double xmin_, double ymin_, double xmax_,
double ymax_, double tilex, double tiley )
throws Exception {
DecimalFormat fo = new DecimalFormat( "#.0" );
String sx = fo.format( xmin_ * 1000 );
sx = sx.substring( 0, sx.length() - 3 ) + "0";
String sy = fo.format( ymin_ * 1000 );
sy = sy.substring( 0, sy.length() - 3 ) + "0";
String file = dir + "/" + sx + "_" + sy + ".wld";
FileWriter fos = new FileWriter( file );
fos.write( ( xmax_ - xmin_ ) / tilex + "\n" );
fos.write( "0\n" );
fos.write( "0\n" );
fos.write( ( ymin_ - ymax_ ) / tiley + "\n" );
fos.write( xmin_ + "\n" );
fos.write( ymax_ + "\n" );
fos.close();
}
/**
* stores one image (tile) in the desired format to the desired target directory.
*/
private String saveTile( String dir, double x, double y, BufferedImage img )
throws Exception {
DecimalFormat fo = new DecimalFormat( "#.0" );
String sx = fo.format( x * 1000 );
sx = sx.substring( 0, sx.length() - 3 ) + "0";
String sy = fo.format( y * 1000 );
sy = sy.substring( 0, sy.length() - 3 ) + "0";
count++;
// String file = dir + "/tile_" + (startIndex++) + "." + targetFormat;
// String filename = dir + "/" + sx + "_" + sy + "." + targetFormat;
String filename = "";
if ( targetFormat.equalsIgnoreCase( "geotiff" ) )
filename = dir + "/" + sx + "_" + sy + ".tif";
else
filename = dir + "/" + sx + "_" + sy + "." + targetFormat;
File file = new File( filename );
file.getParentFile().mkdirs();
FileOutputStream fos = new FileOutputStream( file );
if ( targetFormat.equalsIgnoreCase( "bmp" ) ) {
Encoders.encodeBmp( fos, img );
} else if ( targetFormat.equalsIgnoreCase( "gif" ) ) {
Encoders.encodeGif( fos, img );
} else if ( targetFormat.equalsIgnoreCase( "png" ) ) {
Encoders.encodePng( fos, img );
} else if ( targetFormat.equalsIgnoreCase( "tiff" )
|| targetFormat.equalsIgnoreCase( "tif" )
|| targetFormat.equalsIgnoreCase( "geotiff" ) ) {
Encoders.encodeTiff( fos, img );
} else if ( targetFormat.equalsIgnoreCase( "jpg" )
|| targetFormat.equalsIgnoreCase( "jpeg" ) ) {
Encoders.encodeJpeg( fos, img, quality );
}
fos.close();
return file.toString();
}
/**
* adds a new grid coverage layer to a WCS
*/
private void updateBounds( double llminx, double llminy, double llmaxx, double llmaxy,
double minx, double miny, double maxx, double maxy ) {
/*
* // check to see if we need to update global variables if (AutoTiler.MIN_X_LL == -1 ||
* llminx < AutoTiler.MIN_X_LL) AutoTiler.MIN_X_LL = llminx; if (AutoTiler.MIN_Y_LL == -1 ||
* llminy < AutoTiler.MIN_Y_LL) AutoTiler.MIN_Y_LL = llminy; if (llmaxx >
* AutoTiler.MAX_X_LL) AutoTiler.MAX_X_LL = llmaxx; if (llmaxy > AutoTiler.MAX_Y_LL)
* AutoTiler.MAX_Y_LL = llmaxy; if (AutoTiler.MIN_X_SPATIAL == -1 ||minx <
* AutoTiler.MIN_X_SPATIAL) AutoTiler.MIN_X_SPATIAL = minx; if (AutoTiler.MIN_Y_SPATIAL ==
* -1 ||miny < AutoTiler.MIN_Y_SPATIAL) AutoTiler.MIN_Y_SPATIAL = miny; if (maxx >
* AutoTiler.MAX_X_SPATIAL) AutoTiler.MAX_X_SPATIAL = maxx; if (maxy >
* AutoTiler.MAX_Y_SPATIAL) AutoTiler.MAX_Y_SPATIAL = maxy; // update the ordinates
* AutoTiler.WCS_ORDINATE_HIGH_X += width; AutoTiler.WCS_ORDINATE_HIGH_Y += height;
*/
AutoTiler.MIN_X_LL = llminx;
AutoTiler.MIN_Y_LL = llminy;
AutoTiler.MAX_X_LL = llmaxx;
AutoTiler.MAX_Y_LL = llmaxy;
AutoTiler.MIN_X_SPATIAL = minx;
AutoTiler.MIN_Y_SPATIAL = miny;
AutoTiler.MAX_X_SPATIAL = maxx;
AutoTiler.MAX_Y_SPATIAL = maxy;
}
/**
* @param args
* the command line arguments
*/
public static void main( String[] args ) {
HashMap map = new HashMap();
for ( int i = 0; i < args.length; i += 2 ) {
map.put( args[i], args[i + 1] );
}
if ( !validate( map ) ) {
printHelp();
System.exit( 1 );
}
String inDir = ( (String) map.get( "-i" ) ).trim();
if ( !inDir.endsWith( "/" ) ) {
inDir = inDir + "/";
}
if ( map.get( "-c" ) != null ) {
createDescFile( (String) map.get( "-c" ), inDir );
System.exit( 0 );
}
String outDir = ( (String) map.get( "-o" ) ).trim();
if ( !outDir.endsWith( "/" ) ) {
outDir = outDir + "/";
}
String format = ( (String) map.get( "-f" ) ).toUpperCase();
double[] targetRes = null;
if ( map.get( "-a" ) == null ) {
try {
StringTokenizer st = new StringTokenizer( (String) map.get( "-r" ), ",; " );
targetRes = new double[st.countTokens()];
// TODO search all tiff files in directory and
// obtain lowest resolution, at the moment assume
// all the tiff files are the same resolution
for ( int i = 0; i < targetRes.length; i++ ) {
double v = Double.parseDouble( st.nextToken() );
targetRes[i] = v;
}
} catch ( Exception e ) {
LOG.logError( "Can't parse target resolutions!", e );
printHelp();
System.exit( 1 );
}
}
float quality = 1.0f;
try {
quality = Float.parseFloat( (String) map.get( "-q" ) );
if ( quality > 1 ) {
quality = 1.0f;
} else if ( quality < 0.1 ) {
quality = 0.1f;
}
} catch ( Exception ex ) {
}
String crs = (String) map.get( "-k" );
if ( crs == null ) {
crs = "EPSG:4326";
}
String antialising = (String) map.get( "-antialising" );
try {
File file = new File( inDir );
String[] list = file.list( new DFileFilter() );
IDescReader reader = null;
if ( map.get( "-qt" ) == null )
reader = new CommandLineReader();
else
reader = new TextFileReader( (String) map.get( "-qt" ) );
for ( int i = 0; i < list.length; i++ ) {
int pos = list[i].lastIndexOf( '.' );
String oDir = outDir + list[i].substring( 0, pos );
String inFile = inDir + list[i];
String desc = reader.getDesc( list[i] );
String keywds = reader.getKeywords( list[i] );
AutoTiler tiler = new AutoTiler( inFile, oDir, format, targetRes,
quality, crs, antialising );
tiler.description = desc;
tiler.keywords = keywds;
LOG.logInfo( "output directory: " + outDir );
if ( ( map.get( "-a" ) != null ) && targetRes == null ) {
// automatically calculate the target resolutions
if ( tiler.geoTiff ) {
// get the modelPixelScaleTag
TIFFDirectory tifDir = (TIFFDirectory) tiler.rop.getDynamicProperty( "tiff_directory" );
TIFFField modelPixelScaleTag = tifDir.getField( GeoTiffTag.ModelPixelScaleTag );
double resX = modelPixelScaleTag.getAsDouble( 0 );
double resY = modelPixelScaleTag.getAsDouble( 1 );
double groundRes = ( resX + resY ) / 2.0;
// get the number of levels
int levels = Integer.parseInt( (String) map.get( "-a" ) );
targetRes = new double[levels];
for ( int j = 0; j < levels; j++ ) {
targetRes[j] = groundRes * Math.pow( 2, j );
}
tiler.setTargetResolutions( targetRes );
} else {
double groundRes = ( tiler.resx + tiler.resy ) / 2.0;
int levels = Integer.parseInt( (String) map.get( "-a" ) );
targetRes = new double[levels];
for ( int j = 0; j < levels; j++ ) {
targetRes[j] = groundRes * Math.pow( 2, j );
}
tiler.setTargetResolutions( targetRes );
}
}
tiler.createTileImageTree();
tiler.createConfigurationFile();
// update capabilities XML
String path = (String) map.get( "-u" );
if ( path != null ) {
File capabFile = new File( path );
if ( capabFile.isFile() ) {
tiler.updateCapabilitiesFile( capabFile );
}
}
}
} catch ( Exception e ) {
e.printStackTrace();
}
// force reload of Deegree context
}
/**
* @param string
*/
private static void createDescFile( String filename, String inputDir ) {
LOG.logInfo( "Creating description file " + filename );
try {
PrintWriter writer = new PrintWriter( new FileWriter( new File( filename ) ) );
writer.println( "data,description,keywords (semi-colon separated)" );
File file = new File( inputDir );
String[] list = file.list( new DFileFilter() );
for ( int i = 0; i < list.length; i++ ) {
writer.println( list[i] + "," );
}
writer.close();
writer = null;
} catch ( IOException e ) {
LOG.logError( "Unable to create new description file : " + filename, e );
}
}
/**
*
*/
private void updateCapabilitiesFile( File capabilitiesFile ) {
InputStream inputXSL = AutoTiler.class.getResourceAsStream( AutoTiler.CAPABS_XSL );
if ( inputXSL == null ) {
LOG.logError( "Unable to make capabilities file" );
System.exit( 1 );
}
try {
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer( new StreamSource( inputXSL ) );
transformer.setParameter( "dataDirectory", targetDir );
transformer.setParameter( "configFile",
( new File( configurationFilename ) ).toURL().toString() );
transformer.setParameter( "name", name );
transformer.setParameter( "label", name );
transformer.setParameter( "upperleftll", String.valueOf( AutoTiler.MIN_X_LL ) + ","
+ String.valueOf( AutoTiler.MIN_Y_LL ) );
transformer.setParameter( "lowerrightll", String.valueOf( AutoTiler.MAX_X_LL ) + ","
+ String.valueOf( AutoTiler.MAX_Y_LL ) );
transformer.setParameter( "description", description );
transformer.setParameter( "keywords", keywords );
BufferedReader reader = new BufferedReader( new FileReader( capabilitiesFile ) );
String inputStr = new String();
String tempStr = new String();
while ( ( tempStr = reader.readLine() ) != null ) {
inputStr = inputStr.concat( tempStr );
}
StringReader input = new StringReader( inputStr );
StringWriter result = new StringWriter();
transformer.transform( new StreamSource( input ), new StreamResult( result ) );
// write the result
FileOutputStream fos = new FileOutputStream( capabilitiesFile );
fos.write( result.toString().getBytes( CharsetUtils.getSystemCharset() ) );
fos.close();
} catch ( Exception e ) {
e.printStackTrace();
}
}
/**
*
*/
private void createConfigurationFile() {
URL configURL = AutoTiler.class.getResource( AutoTiler.CONFIG_TEMPLATE );
URL configXSL = AutoTiler.class.getResource( AutoTiler.CONFIG_XSL );
if ( configURL == null || configXSL == null ) {
LOG.logError( "Unable to make configuration file" );
System.exit( 1 );
} else {
// copy this file to the target directory
try {
name = imageName.substring( 0, imageName.indexOf( "." ) );
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 param = new HashMap();
param.put( "upperleftll", String.valueOf( AutoTiler.MIN_X_LL ) + ","
+ String.valueOf( MIN_Y_LL ) );
param.put( "lowerrightll", String.valueOf( AutoTiler.MAX_X_LL ) + ","
+ String.valueOf( MAX_Y_LL ) );
param.put( "upperleft", String.valueOf( AutoTiler.MIN_X_SPATIAL ) + ","
+ String.valueOf( MIN_Y_SPATIAL ) );
param.put( "lowerright", String.valueOf( AutoTiler.MAX_X_SPATIAL ) + ","
+ String.valueOf( MAX_Y_SPATIAL ) );
param.put( "dataDir", targetDir );
param.put( "label", name );
param.put( "name", name );
param.put( "description", description );
param.put( "keywords", keywords );
param.put( "resolutions", resolutions );
param.put( "mimeType", getMimeType() );
param.put( "srs", crs );
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_" + name + "_configuration.xml";
FileOutputStream fos = new FileOutputStream( targetDir + dstFilename );
xml.write( fos );
fos.close();
} catch ( Exception e1 ) {
e1.printStackTrace();
}
} catch ( Exception e ) {
e.printStackTrace();
LOG.logError( "Unable to create configuration files", e );
System.exit( 1 );
}
}
}
/**
*
*/
private static void printHelp() {
System.out.println( "ERROR: List of submitted parameters isn't complete." );
System.out.println();
System.out.println( "TileImageTree parameters: " );
System.out.println( "-i: input directory containing the image(s) " );
System.out.println( " to be tiled (mandatory)" );
System.out.println( "-o: output directory path name (mandatory)" );
System.out.print( "-f: output format (gif, bmp, jpg, png, tif)" );
System.out.println( " default = jpg; " );
System.out.println( " Consider that the target format must have the" );
System.out.println( " same or a higher color depth then the input format" );
System.out.println( "-r comma sperated list of resolutions; e.g. 1.0,0.5,0.25" );
System.out.println( " The length of the list is equal to the number of levels" );
System.out.println( " that will be generated. The number of level determines the" );
System.out.println( " size of the generated tiles because for each level the tiles" );
System.out.println( " of the former level will be devided into four quarters." );
System.out.println( " The first level will have the first resolution, the second." );
System.out.println( " level the second one etc.." );
System.out.println( "-s: index where the nameing of the tiles start" );
System.out.println( " (optional, default = 0)" );
System.out.println( "-q: qualitiy of the produced tiles (just if output format = jpg)" );
System.out.println( " (optional, default = 1 (best))" );
System.out.println( "-k: coordinate reference system of the map to be tiled" );
System.out.println( " (optional, default = EPSG:4326)" );
System.out.println( "-a: Automatic deployment, calculates target resolutions "
+ "from Geotiff files, specify no. of levels" );
System.out.println( "-u: update capabilities XML, pass in path to wcs_capabilities.xml" );
System.out.println( "-qt: quiet mode, input descriptions are read in from "
+ "the associated file name \n usage: -q TextFileName" );
System.out.println( "-c: create a new text file for use with quiet mode" );
}
/**
* default progress observer class. write the progress in % to the console
*/
public class ProgressObserver {
private int max = 0;
/**
* Creates a new ProgressObserver object.
*/
public ProgressObserver() {
if ( targetResolutions != null )
for ( int i = 0; i < targetResolutions.length; i++ ) {
max += (int) Math.pow( 4, ( i + 1 ) );
}
}
/**
* @param object
*/
public void write( Object object ) {
double v = ( (Integer) object ).intValue();
if ( ( v % 30 ) == 0 ) {
// System.gc();
v = (int) Math.round( v / max * 10000d );
System.out.println( ( v / 100d ) + "%" );
}
}
}
/**
* @version $Revision: 1.27 $
* @author <a href="mailto:poth@lat-lon.de">Andreas Poth </a>
*/
private static class DFileFilter implements FilenameFilter {
/**
* @return
*/
public boolean accept( File f, String name ) {
int pos = name.lastIndexOf( "." );
String ext = name.substring( pos + 1 );
return ext.toUpperCase().equals( "JPG" ) || ext.toUpperCase().equals( "TIFF" )
|| ext.toUpperCase().equals( "TIF" ) || ext.toUpperCase().equals( "GIF" )
|| ext.toUpperCase().equals( "PNG" ) || ext.toUpperCase().equals( "BMP" )
|| ext.toUpperCase().equals( "JPEG" );
}
}
/**
* @param progressObserver
* The progressObserver to set.
*/
public void setProgressObserver( ProgressObserver progressObserver ) {
this.progressObserver = progressObserver;
}
/**
* @param mimeType
* The mimeType to set.
*/
public String getMimeType() {
if ( targetFormat.equalsIgnoreCase( "bmp" ) ) {
mimeType = "image/bmp";
} else if ( targetFormat.equalsIgnoreCase( "gif" ) ) {
mimeType = "image/gif";
} else if ( targetFormat.equalsIgnoreCase( "png" ) ) {
mimeType = "image/png";
} else if ( targetFormat.equalsIgnoreCase( "tiff" )
|| targetFormat.equalsIgnoreCase( "tif" ) ) {
// have to check bit depth here
if ( bitDepth != null )
mimeType = "image/GeoTIFF";
else
mimeType = "image/tiff";
} else if ( targetFormat.equalsIgnoreCase( "jpg" )
|| targetFormat.equalsIgnoreCase( "jpeg" ) ) {
mimeType = "image/jpeg";
} else if ( targetFormat.equalsIgnoreCase( "geotiff" ) ) {
mimeType = "image/GeoTIFF";
}
// return mimeType;
return targetFormat;
}
}
interface IDescReader {
String getDesc( String filename );
String getKeywords( String dataName );
}
class CommandLineReader implements IDescReader {
BufferedReader reader = null;
public String getDesc( String filename ) {
/*
* String description = ""; // prompt the user to enter their name System.out.print("Enter a
* description for the data " + filename + ": ");
* // open up standard input BufferedReader br = getBufferedReader();
*
* try { description = br.readLine(); } catch (IOException ioe) { System.out.println("IO
* error trying to read your description!"); System.exit(1); } return description;
*/
return "";
}
public String getKeywords( String filename ) {
/*
* String keywords = ""; System.out.print("Enter a comma separated list of keywords for the
* data " + filename + ": "); try { BufferedReader br = getBufferedReader(); keywords =
* br.readLine(); } catch (IOException ioe) { System.out.println("IO error trying to read
* your keywords!"); System.exit(1); } return keywords;
*/
return "";
}
}
class TextFileReader implements IDescReader {
private String separator = ",";
private String keywordSeparator = ";";
private BufferedReader input = null;
private HashMap map = new HashMap();
public TextFileReader( String filename ) {
try {
input = new BufferedReader( new FileReader( filename ) );
String line = null;
// skip the header
input.readLine();
while ( ( line = input.readLine() ) != null ) {
StringTokenizer tokenizer = new StringTokenizer( line, separator );
String dataname = (String) tokenizer.nextElement();
String remainder = line.substring( dataname.length() + 1 );
map.put( dataname, remainder );
}
input.close();
} catch ( FileNotFoundException e ) {
System.out.println( "Failed to read in descriptions and keywords from file " + filename );
} catch ( IOException ioe ) {
ioe.printStackTrace();
}
}
/*
* (non-Javadoc)
*
* @see org.deegree.tools.raster.IDescReader#getDesc(int)
*/
public String getDesc( String dataName ) {
String result = (String) map.get( dataName );
if ( result != null ) {
// format is 'description, ...'
StringTokenizer tokenizer = new StringTokenizer( result, separator );
return (String) tokenizer.nextElement();
}
return "";
}
/*
* (non-Javadoc)
*
* @see org.deegree.tools.raster.IDescReader#getKeywords(int)
*/
public String getKeywords( String dataName ) {
String result = (String) map.get( dataName );
if ( result != null ) {
// format is 'description, keywords'
StringTokenizer tokenizer = new StringTokenizer( result, separator );
tokenizer.nextElement();
String keywords = (String) tokenizer.nextElement();
keywords = keywords.replaceAll( keywordSeparator, separator );
return keywords;
}
return "";
}
public void finalize() {
try {
input.close();
} catch ( IOException e ) {
e.printStackTrace();
}
input = null;
}
}
/* ********************************************************************
Changes to this class. What the people have been up to:
$Log: AutoTiler.java,v $
Revision 1.27 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.26 2006/10/17 20:31:19 poth
*** empty log message ***
Revision 1.25 2006/09/27 16:46:41 poth
transformation method signature changed
Revision 1.24 2006/08/07 06:55:06 poth
never read variables removed
Revision 1.23 2006/08/07 06:54:39 poth
never read variables removed
Revision 1.22 2006/08/07 06:52:28 poth
unnecessary else blocks removed
Revision 1.21 2006/07/12 14:46:18 poth
comment footer added
********************************************************************** */