//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/tools/raster/Text2Tiff.java,v 1.18 2006/11/16 17:10:44 poth Exp $ /*---------------- 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 Aennchenstr. 19 53177 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@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.tools.raster; import java.awt.color.ColorSpace; import java.awt.image.BufferedImage; import java.awt.image.ColorModel; import java.awt.image.ComponentColorModel; import java.awt.image.DataBuffer; import java.awt.image.Raster; import java.awt.image.WritableRaster; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; import java.io.FileReader; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Hashtable; import java.util.List; import java.util.StringTokenizer; import org.deegree.datatypes.values.Interval; import org.deegree.datatypes.values.TypedLiteral; import org.deegree.datatypes.values.Values; import org.deegree.framework.util.ImageUtils; import org.deegree.io.quadtree.IndexException; import org.deegree.io.quadtree.MemPointQuadtree; import org.deegree.io.quadtree.Quadtree; import org.deegree.model.spatialschema.Envelope; import org.deegree.model.spatialschema.GeometryFactory; import org.deegree.model.spatialschema.Point; import org.deegree.processing.raster.interpolation.DataTuple; import org.deegree.processing.raster.interpolation.InterpolationException; import org.deegree.processing.raster.interpolation.InverseDistanceToPower; /** * This class converts geodata and special values from a simple text file format to a .tiff file * format. The values are written as 32 bit float values. The <code>main</code> method should be * used to utilise this class as a command line tool. * * * @version $Revision: 1.18 $ * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a> * @author last edited by: $Author: poth $ * * @version 1.0. $Revision: 1.18 $, $Date: 2006/11/16 17:10:44 $ * * @since 2.0 */ public class Text2Tiff { // parameters private int columnNumber = 0; private double resolution = 0; private String columnName = null; private List<String> inputFilenames = new ArrayList<String>(); private String outputFilename = null; private boolean readHeader = false; private boolean oracle = false; private Envelope boundingBox = null; private boolean interpolate = false; private boolean use32Bits = false; // data private BufferedReader in; private Raster raster; private Quadtree quadtree; private BufferedImage image; private int imageWidth; private int imageHeight; private DataBuffer buffer; // interpolating options private double interpolatePower = 2; private int interpolateMinData = 5; private int interpolateMaxData = 20; private double interpolateNoValue = 0; private double interpolateRadiusX = 2; private double interpolateRadiusY = 2; private double interpolateRadiusIncreaseX = 0; private double interpolateRadiusIncreaseY = 0; private Values ignoreValues = null; /** * The only constructor, called usually by the main method with command line arguments. * * @param args */ public Text2Tiff( String[] args ) { if ( args.length < 3 ) { printUsage( "Not enough arguments." ); } parseArgs( args ); // check for consistency if ( ( columnName != null ) && !readHeader ) { printUsage( "If a column name is given, I have to read the header!" ); } if ( inputFilenames.size() == 0 ) { printUsage( "No input filename given." ); } if ( outputFilename == null ) { printUsage( "No output filename given." ); } if ( columnName == null && columnNumber == 0 ) { printUsage( "No column specified." ); } if ( !readHeader ) { --columnNumber; } } private void parseArgs( String[] args ) { List<Interval> intervals = new ArrayList<Interval>(); // parse options try { for ( int i = 0; i < ( args.length - 1 ); ++i ) { if ( args[i].equals( "--image-type" ) ) { use32Bits = args[i + 1].equals( "32" ); ++i; } else if ( args[i].equals( "--image-width" ) ) { imageWidth = Integer.parseInt( args[i + 1] ); ++i; } else if ( args[i].equals( "--image-height" ) ) { imageHeight = Integer.parseInt( args[i + 1] ); ++i; } else if ( args[i].equals( "-c" ) || args[i].equals( "--column-number" ) ) { columnNumber = Integer.parseInt( args[i + 1] ); ++i; } else if ( args[i].equals( "-h" ) || args[i].equals( "--no-read-header" ) ) { readHeader = false; } else if ( args[i].equals( "-o" ) || args[i].equals( "--oracle" ) ) { oracle = true; } else if ( args[i].equals( "+h" ) || args[i].equals( "--read-header" ) ) { readHeader = true; } else if ( args[i].equals( "-cn" ) || args[i].equals( "--column-name" ) ) { columnName = args[i + 1]; ++i; } else if ( args[i].equals( "-r" ) || args[i].equals( "--resolution" ) ) { resolution = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "-i" ) || args[i].equals( "--interpolate" ) ) { interpolate = true; } else if ( args[i].equals( "--interpolate-power" ) ) { interpolatePower = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-min-data" ) ) { interpolateMinData = Integer.parseInt( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-max-data" ) ) { interpolateMaxData = Integer.parseInt( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-no-value" ) ) { interpolateNoValue = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-radius-x" ) ) { interpolateRadiusX = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-radius-y" ) ) { interpolateRadiusY = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-radius-increase-x" ) ) { interpolateRadiusIncreaseX = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-radius-increase-y" ) ) { interpolateRadiusIncreaseY = Double.parseDouble( args[i + 1] ); ++i; } else if ( args[i].equals( "--interpolate-ignore-range" ) ) { TypedLiteral min = new TypedLiteral( args[i + 1], null ); TypedLiteral max = new TypedLiteral( args[i + 2], null ); Interval interval = new Interval( min, max, null, null, null ); intervals.add( interval ); i += 2; } else if ( args[i].equals( "--help" ) ) { printUsage( null ); } else if ( args[i].equals( "-help" ) ) { printUsage( null ); } else { inputFilenames.add( args[i] ); } } } catch ( NumberFormatException nfe ) { printUsage( "Illegal argument, number expected." ); } // get file names outputFilename = args[args.length - 1]; if ( intervals.size() != 0 ) { ignoreValues = new Values( intervals.toArray( new Interval[intervals.size()] ), null, null ); } } // reads the first line private void readHeader() throws IOException { if ( !readHeader ) { return; } String s = in.readLine(); columnNumber = 0; // get the right index for the column if ( columnName != null ) { StringTokenizer tok = new StringTokenizer( s ); while ( tok.hasMoreTokens() ) { String t = tok.nextToken(); if ( t.equals( columnName ) ) { break; } ++columnNumber; } } else { --columnNumber; } } // reads all data into the array lists private ArrayList<DataTuple> readValues( String filename ) throws IOException, NumberFormatException { readHeader(); File file = new File( filename ); int size = (int) ( file.length() / 30 ); ArrayList<DataTuple> values = new ArrayList<DataTuple>( size ); BufferedReader in = new BufferedReader( new FileReader( filename ) ); int counter = 0; while ( in.ready() ) { StringTokenizer tokenizer = new StringTokenizer( in.readLine() ); int idx = 0; double x = 0; double y = 0; while ( tokenizer.hasMoreTokens() ) { if ( idx == 0 ) { x = Double.parseDouble( tokenizer.nextToken() ); } else if ( idx == 1 ) { y = Double.parseDouble( tokenizer.nextToken() ); } else if ( idx == columnNumber ) { values.add( new DataTuple( x, y, Double.parseDouble( tokenizer.nextToken() ) ) ); break; } else tokenizer.nextToken(); ++idx; } if ( ++counter % 10000 == 0 ) { System.out.print( "Read " + counter / 1000 + " thousand lines.\r" ); } } System.out.println(); in.close(); return values; } // calculate resolution and bbox private void preprocessFiles() throws IOException { double minx = Double.MAX_VALUE; double miny = Double.MAX_VALUE; double maxx = Double.MIN_VALUE; double maxy = Double.MIN_VALUE; boolean calcResolution = ( resolution == 0 ); if ( imageWidth != 0 && imageHeight != 0 ) { calcResolution = false; } if ( calcResolution ) { resolution = Double.MAX_VALUE; } for ( String filename : inputFilenames ) { System.out.println( "Reading file " + filename ); ArrayList<DataTuple> values = readValues( filename ); // Collections.sort( values ); double[] ys = null; DataTuple prev = null; double cur; if ( calcResolution ) { ys = new double[values.size()]; prev = values.get( 0 ); cur = 0; } for ( int i = 0; i < values.size(); ++i ) { DataTuple tuple = values.get( i ); if ( maxx < tuple.x ) { maxx = tuple.x; } if ( maxy < tuple.y ) { maxy = tuple.y; } if ( minx > tuple.x ) { minx = tuple.x; } if ( miny > tuple.y ) { miny = tuple.y; } if ( calcResolution ) { cur = Math.abs( tuple.x - prev.x ); if ( ( cur != 0 ) && ( resolution > cur ) ) { resolution = cur; } ys[i] = tuple.y; prev = tuple; } } if ( calcResolution ) { Arrays.sort( ys ); for ( int i = 0; i < ys.length - 1; ++i ) { cur = Math.abs( ys[i] - ys[i + 1] ); if ( cur != 0 && cur < resolution ) { resolution = cur; } } } } System.out.println( "Covered area:" ); System.out.println( minx + " - " + maxx ); System.out.println( miny + " - " + maxy ); boundingBox = GeometryFactory.createEnvelope( minx, miny, maxx, maxy, null ); if ( !calcResolution && resolution == 0 ) { resolution = Math.abs( ( maxy - miny ) / ( imageHeight + 1 ) ); double h = Math.abs( ( maxx - minx ) / ( imageWidth + 1 ) ); if ( h < resolution ) { resolution = h; } } if ( imageWidth == 0 && imageHeight == 0 ) { imageWidth = (int) ( boundingBox.getWidth() / resolution ) + 1; imageHeight = (int) ( boundingBox.getHeight() / resolution ) + 1; } System.gc(); System.out.println( "Resolution: " + resolution ); } // creates the buffered image with the right size private void createImage() { ColorModel ccm; if ( use32Bits ) { image = new BufferedImage( imageWidth, imageHeight, BufferedImage.TYPE_INT_ARGB ); } else { ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false, BufferedImage.OPAQUE, DataBuffer.TYPE_USHORT ); WritableRaster wr = ccm.createCompatibleWritableRaster( imageWidth, imageHeight ); image = new BufferedImage( ccm, wr, false, new Hashtable() ); } raster = image.getData(); buffer = raster.getDataBuffer(); } // calculates the index of the desired position (in regard to a DataBuffer of a Raster) private int calculatePosition( double x, double y ) { double tmp = ( x - boundingBox.getMin().getX() ) / resolution; double ypos = imageHeight - ( ( y - boundingBox.getMin().getY() ) / resolution ) - 1; return (int) Math.round( tmp + ( ypos * imageWidth ) ); } // inserts all values into the image private void insertValue( double x, double y, double val ) { int pos = Math.abs( calculatePosition( x, y ) ); if ( use32Bits ) { buffer.setElem( pos, Float.floatToIntBits( (float) val ) ); } else { buffer.setElem( pos, (int) Math.round( ( val * 10 ) ) ); } } // creates the worldfile, depends on minimum values (call after createImage) private void writeWorldfile() throws IOException { PrintStream out = new PrintStream( new FileOutputStream( outputFilename + ".tfw" ) ); out.println( resolution ); out.println( "0.0" ); out.println( "0.0" ); out.println( -resolution ); if ( oracle ) { out.println( boundingBox.getMin().getX() - resolution / 2 ); } else { out.println( boundingBox.getMin().getX() ); } if ( oracle ) { out.println( boundingBox.getMax().getY() + resolution / 2 ); } else { out.println( boundingBox.getMax().getY() ); } out.println(); } private void buildQuadtree() throws IOException, IndexException { for ( String filename : inputFilenames ) { readHeader(); BufferedReader in = new BufferedReader( new FileReader( filename ) ); int counter = 0; while ( in.ready() ) { StringTokenizer tokenizer = new StringTokenizer( in.readLine() ); int idx = 0; double x = 0; double y = 0; while ( tokenizer.hasMoreTokens() ) { if ( idx == 0 ) { x = Double.parseDouble( tokenizer.nextToken() ); } else if ( idx == 1 ) { y = Double.parseDouble( tokenizer.nextToken() ); } else if ( idx == columnNumber ) { Point point = GeometryFactory.createPoint( x, y, null ); quadtree.insert( new DataTuple( x, y, Double.parseDouble( tokenizer.nextToken() ) ), point ); break; } else tokenizer.nextToken(); ++idx; } if ( ++counter % 10000 == 0 ) { System.out.print( "Read " + counter / 1000 + " thousand lines.\r" ); } } in.close(); System.out.println(); } } /** * This method executes all steps that are required to transform the text file into a tiff file. * */ private void transform() { try { preprocessFiles(); quadtree = new MemPointQuadtree( boundingBox ); buildQuadtree(); createImage(); interpolate(); image.setData( raster ); System.out.println( "Writing output files..." ); ImageUtils.saveImage( image, new File( outputFilename + ".tif" ), 1 ); writeWorldfile(); System.out.println( "Done." ); // testOutput(); } catch ( IOException ioe ) { System.out.println( "Could not read or write a file, reason:" ); ioe.printStackTrace(); System.exit( 0 ); } catch ( NumberFormatException nfe ) { System.out.println( "A number could not be parsed correctly. Reason: " ); nfe.printStackTrace(); System.exit( 0 ); } catch ( InterpolationException e ) { System.out.println( "Could not interpolate missing values. Reason: " ); e.printStackTrace(); System.exit( 0 ); } catch ( IndexException e ) { System.out.println( "Could not build Quadtree. Reason: " ); e.printStackTrace(); System.exit( 0 ); } } private void interpolate() throws InterpolationException { InverseDistanceToPower interpolator = new InverseDistanceToPower( quadtree, ignoreValues, interpolateRadiusX, interpolateRadiusY, 0, interpolateMinData, interpolateMaxData, interpolateNoValue, interpolateRadiusIncreaseX, interpolateRadiusIncreaseY, interpolatePower ); double minx = boundingBox.getMin().getX(); double miny = boundingBox.getMin().getY(); int count = imageWidth * imageHeight; int counter = 0; int interpolatedCounter = 0; for ( int xipos = 0; xipos < imageWidth; ++xipos ) { for ( int yipos = 0; yipos < imageHeight; ++yipos ) { double xpos = minx + resolution * xipos; double ypos = miny + resolution * yipos; Envelope env = GeometryFactory.createEnvelope( xpos - 0.01, ypos - 0.01, xpos + 0.01, ypos + 0.01, null ); try { List list = quadtree.query( env ); double val = 0; if ( list.size() == 0 ) { if ( interpolate ) { val = interpolator.calcInterpolatedValue( xpos, ypos ); ++interpolatedCounter; } } else { val = ( (DataTuple) list.get( 0 ) ).value; } insertValue( xpos, ypos, val ); } catch ( IndexException e ) { throw new InterpolationException( "Could not interpolate.", e ); } if ( ++counter % 1000 == 0 ) { System.out.print( counter + "/" + count + "\r" ); } } } System.out.println( counter + '/' + count + ", interpolated " + interpolatedCounter + " values" ); } /** * Prints out an error message and general usage information of the tool. * * @param error * an error message */ public void printUsage( String error ) { if ( error != null ) { System.out.println( "Error: " + error ); System.out.println(); } System.out.println( "java Text2Tiff <options> <inputfile[s]> <outputfile>" ); System.out.println( "Options:" ); System.out.println(); System.out.println( " --help, -help:" ); System.out.println( " print this message" ); System.out.println( " --read-header, +h:" ); System.out.println( " --no-read-header, -h:" ); System.out.println( " Do/Do not read a header line in the input file. If enabled," ); System.out.println( " one can specify column names instead of column numbers as" ); System.out.println( " seen below. Default is no." ); System.out.println( " --column-number n, -c n:" ); System.out.println( " Use the column number n as input column. Must be specified" ); System.out.println( " if column name below is not given. Counting starts with one," ); System.out.println( " so '3' means actually the third column, not the fourth." ); System.out.println( " --column-name n, -cn n:" ); System.out.println( " Use the column named n as input column. Must be specified" ); System.out.println( " if no column number is given." ); System.out.println( " --oracle, -o:" ); System.out.println( " Write the worldfile as Oracle expects it, using the outer" ); System.out.println( " bounds of the bbox and not the point centers. Default is no." ); System.out.println( " --image-type n:" ); System.out.println( " n can be either 16 or 32. If n is 16, an image of type USHORT" ); System.out.println( " will be created, and the values will be stored as shorts," ); System.out.println( " multiplied by 10. If n is 32, the float values will be" ); System.out.println( " stored in an image of type integer, as can be seen in" ); System.out.println( " Java's Float.floatToIntBits() method. Default is 16." ); System.out.println( " --image-width n:" ); System.out.println( " --image-height n:" ); System.out.println( " If set, an image of this size will be created. If not set" ); System.out.println( " (default), the size will be determined by the resolution" ); System.out.println( " either determined automatically or set by hand." ); System.out.println( " --resolution n, -r n:" ); System.out.println( " Set geo resolution to n. If omitted, the resolution will be" ); System.out.println( " set to the smallest value found in the input data." ); System.out.println( " --interpolate, i:" ); System.out.println( " Interpolate missing values. By default, no interpolation" ); System.out.println( " will be performed." ); System.out.println( " --interpolate-power n:" ); System.out.println( " Interpolate using n as power. Default is " + interpolatePower + "." ); System.out.println( " --interpolate-min-data n:" ); System.out.println( " Interpolate only in the presence of n values within the search" ); System.out.println( " radius. Default is " + interpolateMinData + "." ); System.out.println( " --interpolate-max-data n:" ); System.out.println( " Interpolate using a maximum of n values from within the search" ); System.out.println( " radius. If more values are found, the n nearest will be used." ); System.out.println( " Default is " + interpolateMaxData + "." ); System.out.println( " --interpolate-no-value n:" ); System.out.println( " The value to be used if insufficient data is in the search" ); System.out.println( " radius. See also the radius-increase options below. Default" ); System.out.println( " is " + interpolateNoValue + "." ); System.out.println( " --interpolate-radius-x n:" ); System.out.println( " Interpolate using a search radius of n in the x direction." ); System.out.println( " Default is " + interpolateRadiusX + "." ); System.out.println( " --interpolate-radius-y n:" ); System.out.println( " Interpolate using a search radius of n in the y direction." ); System.out.println( " Default is " + interpolateRadiusY + "." ); System.out.println( " --interpolate-radius-increase-x n:" ); System.out.println( " Automatically increase the x search radius by n if less than" ); System.out.println( " --i-min-data values are found. If specified and not 0, the" ); System.out.println( " value --i-no-value will be ignored. Default is 0." ); System.out.println( " --interpolate-radius-increase-y n:" ); System.out.println( " Automatically increase the y search radius by n if less than" ); System.out.println( " --i-min-data values are found. If specified and not 0, the" ); System.out.println( " value --i-no-value will be ignored. Default is 0." ); System.out.println( " --interpolate-ignore-range min max:" ); System.out.println( " Adds a new range of values to be ignored while interpolating." ); System.out.println(); System.out.println( ".tif/.tfw will be appended to the <outputfile> parameter." ); System.out.println(); if ( error == null ) { System.exit( 0 ); } else { System.exit( 1 ); } } /** * This method is used from the command line. * * @param args * the command line arguments. */ public static void main( String[] args ) { new Text2Tiff( args ).transform(); } } /* ************************************************************************************************* Changes to this class. What the people have been up to: $Log: Text2Tiff.java,v $ Revision 1.18 2006/11/16 17:10:44 poth bug fix - use Math.round instead of (int) Revision 1.17 2006/11/15 16:30:51 schmitz Using BufferedImage.TYPE_INT_ARGB now. Revision 1.16 2006/11/14 13:00:42 poth bug fix - rounding float values to int Revision 1.15 2006/11/08 16:46:20 schmitz Added the --image-type option to choose between ushort 16bit and signed integer 32bit images. Revision 1.14 2006/10/30 08:06:28 poth bug fix - input streams closed Revision 1.13 2006/10/27 07:45:42 schmitz Altered the program to create a unsigned short image, 16 bits and storing the values after multiplying them with 10. Revision 1.12 2006/10/25 11:59:04 schmitz Text2Tiff is unfinished due to problems with geotiff format. The rest of the interpolation/Text2Tiff should work fine now. Revision 1.11 2006/10/20 14:57:08 schmitz Added a memory point quadtree implementation. Used the quadtree for interpolation. Updated the text2tiff tool to use quadtree and interpolation. Revision 1.10 2006/10/10 07:44:54 schmitz Formatting. Revision 1.9 2006/10/10 07:42:51 schmitz Default printing of usage information. Revision 1.8 2006/09/26 14:25:23 poth class header corrected Revision 1.7 2006/08/23 21:04:34 poth *** empty log message *** Revision 1.6 2006/08/23 21:04:21 poth bug fix Revision 1.5 2006/08/20 20:50:42 poth bug fix - setting raster value / creating world file (y-coordinate) Revision 1.4 2006/07/29 08:53:02 poth help text corrected Revision 1.3 2006/07/11 09:00:43 schmitz New version with oracle support. ************************************************************************************************ */