//$Header: /home/deegree/jail/deegreerepository/deegree/src/org/deegree/io/geotiff/GeoTiffReader.java,v 1.7 2006/08/06 20:56:20 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
53115 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.io.geotiff;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.HashMap;
import java.util.StringTokenizer;
import org.apache.batik.ext.awt.image.codec.FileCacheSeekableStream;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFDirectory;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFField;
import org.apache.batik.ext.awt.image.codec.tiff.TIFFImage;
import org.deegree.framework.log.ILogger;
import org.deegree.framework.log.LoggerFactory;
import org.deegree.model.spatialschema.Envelope;
import org.deegree.model.spatialschema.GeometryFactory;
/**
* <p>
* <tt>
* TIFF type :: Java type<br>
* TIFF_BYTE :: byte<br>
* TIFF_ASCII :: String<br>
* TIFF_SHORT :: char<br>
* TIFF_LONG :: long<br>
* TIFF_RATIONAL :: long[2]<br>
* TIFF_SBYTE :: byte<br>
* TIFF_UNDEFINED :: byte<br>
* TIFF_SSHORT :: short<br>
* TIFF_SLONG :: int<br>
* TIFF_SRATIONAL :: int[2]<br>
* TIFF_FLOAT :: float<br>
* TIFF_DOUBLE :: double<br>
* </tt>
* <p>
*
* @author <a href="mailto:schaefer@lat-lon.de">Axel Schaefer </A>
* @author last edited by: $Author: poth $
* @version 2.0. $Revision: 1.7 $, $Date: 2006/08/06 20:56:20 $
* @since
*/
public class GeoTiffReader {
private static final ILogger LOG = LoggerFactory.getLogger( GeoTiffReader.class );
TIFFImage image = null;
TIFFDirectory tifdir = null;
HashMap geoKeyDirectoryTag = null;
boolean hasGeoKeyDirectoryTag = false;
/**
* @param file
* @throws FileNotFoundException
* @throws IOException
*/
public GeoTiffReader(File file) throws FileNotFoundException, IOException,
GeoTiffException {
TIFFDecodeParam decodeParam = new TIFFDecodeParam();
int geodirectory = 0;
FileInputStream fis = new FileInputStream(file);
FileCacheSeekableStream fcss = new FileCacheSeekableStream(fis);
this.image = new TIFFImage(fcss, decodeParam, geodirectory);
if (!isGeoTiff(this.image)) {
throw new GeoTiffException(
"GeoTiffReader: TIFF is no GeoTIFF image!");
}
this.tifdir = (TIFFDirectory) image.getProperty("tiff_directory");
if (this.tifdir.getField(GeoTiffTag.GeoKeyDirectoryTag) != null) {
setGeoKeyDirectoryTag();
}
fcss.close();
}
// ***********************************************************************
// General GeoTIFF tags
// ***********************************************************************
/**
* <p>
* GeoKeyDirectoryTag: <br>
* Tag = 34735 (87AF.H) <br>
* Type = SHORT (2-byte unsigned short) <br>
* N = variable, >= 4 <br>
* Alias: ProjectionInfoTag, CoordSystemInfoTag <br>
* Owner: SPOT Image, Inc.
* <p>
* This tag may be used to store the GeoKey Directory, which defines and
* references the "GeoKeys", as described below.
* <p>
* The tag is an array of unsigned SHORT values, which are primarily grouped
* into blocks of 4. The first 4 values are special, and contain GeoKey
* directory header information. The header values consist of the following
* information, in order:
* <p>
* <tt>Header={KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys}</tt>
* <p>
* and as Keys:
* <p>
* <tt>KeyEntry = { KeyID, TIFFTagLocation, Count, Value_Offset }</tt>^
* <p>
* where
* <ul>
* <li>"KeyID" gives the key-ID value of the Key (identical in function to
* TIFF tag ID, but completely independent of TIFF tag-space),
* <li>"TIFFTagLocation" indicates which TIFF tag contains the value(s) of
* the Key: if TIFFTagLocation is 0, then the value is SHORT, and is
* contained in the "Value_Offset" entry. Otherwise, the type (format) of
* the value is implied by the TIFF-Type of the tag containing the value.
* <li>"Count" indicates the number of values in this key.
* <li>"Value_Offset" Value_Offset indicates the index- offset *into* the
* TagArray indicated by TIFFTagLocation, if it is nonzero. If
* TIFFTagLocation=0, then Value_Offset contains the actual (SHORT) value of
* the Key, and Count=1 is implied. Note that the offset is not a
* byte-offset, but rather an index based on the natural data type of the
* specified tag array.</li>
* </ul>
*/
private void setGeoKeyDirectoryTag() {
TIFFField ff = this.tifdir.getField(GeoTiffTag.GeoKeyDirectoryTag);
char[] ch = ff.getAsChars();
// resulting HashMap, containing the key and the array of values
this.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;
LOG.logDebug( "[" + i + "].KEY: " + keydirversion + " \t"
+ keyrevision + "\t" + minorrevision + "\t" + numberofkeys);
this.geoKeyDirectoryTag.put(new Integer(keydirversion), keys);
}
this.hasGeoKeyDirectoryTag = true;
}
/**
* <p>
* GeoDoubleParamsTag: <br>
* Tag = 34736 (87BO.H) <br>
* Type = DOUBLE (IEEE Double precision) <br>
* N = variable <br>
* Owner: SPOT Image, Inc.
* <p>
* This tag is used to store all of the DOUBLE valued GeoKeys, referenced by
* the GeoKeyDirectoryTag. The meaning of any value of this double array is
* determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT
* values should first be converted to DOUBLE and stored here.
*
* @return
*/
public Object getGeoDoubleParamsTag() {
//TIFFField ff = this.tifdir.getField(GeoTiffTag.GeoDoubleParamsTag);
// TODO GeoDoubleParamsTag
return null;
}
/**
* <p>
* GeoAsciiParamsTag: <br>
* Tag = 34737 (87B1.H) <br>
* Type = ASCII <br>
* Owner: SPOT Image, Inc. <br>
* N = variable
* <p>
* This tag is used to store all of the DOUBLE valued GeoKeys, referenced by
* the GeoKeyDirectoryTag. The meaning of any value of this double array is
* determined from the GeoKeyDirectoryTag reference pointing to it. FLOAT
* values should first be converted to DOUBLE and stored here.
* <p>
* A baseline GeoTIFF-reader must check for and convert the final "|" pipe
* character of a key back into a NULL before returning it to the client
* software.
*
*/
public String[] getGeoAsciiParamsTag() {
// TODO: getGeoAsciiParamsTag(int count, int value_offset)!!!
TIFFField field = this.tifdir.getField(GeoTiffTag.GeoAsciiParamsTag);
String gapt = field.getAsString(0);
LOG.logDebug( gapt );
StringTokenizer st = new StringTokenizer(gapt, "|");
LOG.logDebug( "countTokens: " + st.countTokens() );
String[] gapt_fields = new String[st.countTokens()];
int i = 0;
while (st.hasMoreTokens()) {
gapt_fields[i++] = st.nextToken();
}
for (int j = 0; j < gapt_fields.length; j++) {
LOG.logDebug( gapt_fields[j] );
}
return gapt_fields;
}
// ***********************************************************************
// specific GeoTIFF contents GeoTIFF keys
// ***********************************************************************
/**
*
*/
private int[] getVersionInformation() throws GeoTiffException {
int[] content = new int[3];
if (this.geoKeyDirectoryTag.containsKey(new Integer(1))) {
content = (int[]) this.geoKeyDirectoryTag.get(new Integer(1));
} else {
throw new GeoTiffException(
"No GeoTIFF Information found at Tag '1'");
}
return content;
}
/**
*
* @return fixed 1
* @throws GeoTiffException
*/
public int getGeoKeyDirectoryVersion() throws GeoTiffException {
getVersionInformation();
return 1;
}
/**
*
* @return
*/
public String getKeyRevision() throws GeoTiffException {
String key_revision = "";
int[] kv = getVersionInformation();
key_revision = kv[0] + "." + kv[1];
return key_revision;
}
public int getNumberOfKeysInGeoKeyDirectoryTag() throws GeoTiffException {
int[] kv = getVersionInformation();
return kv[2];
}
/**
* <p>
* Key ID = 1024 <br>
* Type: SHORT (code) <br>
* <p>
* This GeoKey defines the general type of model Coordinate system used, and
* to which the raster space will be transformed:unknown, Geocentric (rarely
* used), Geographic, Projected Coordinate System, or user-defined. If the
* coordinate system is a PCS, then only the PCS code need be specified. If
* the coordinate system does not fit into one of the standard registered
* PCS'S, but it uses one of the standard projections and datums, then its
* should be documented as a PCS model with "user-defined" type, requiring
* the specification of projection parameters, etc.
* <p>
* GeoKey requirements for User-Defined Model Type (not advisable):
* GTCitationGeoKey
*
* @return (0) unknown, <br>
* (1) ModelTypeProjected (Projection Coordinate System), <br>
* (2) ModelTypeGeographic (Geographic latitude-longitude System),
* <br>
* (3) ModelTypeGeocentric (Geocentric (X,Y,Z) Coordinate System)
* (rarely used), <br>
* (4?) user-defined
*
* @throws GeoTiffException
*/
public int getGTModelTypeGeoKey() throws GeoTiffException {
int[] content = new int[3];
int key = -99;
if (this.geoKeyDirectoryTag.containsKey(new Integer(
GeoTiffKey.GTModelTypeGeoKey))) {
content = (int[]) this.geoKeyDirectoryTag.get(new Integer(
GeoTiffKey.GTModelTypeGeoKey));
// TIFFTagLocation
if (content[0] == 0) {
// return Value_Offset
key = content[2];
} else {
// TODO other TIFFTagLocation that GeoKeyDirectoryTag
}
} else {
throw new GeoTiffException("No GeoTIFF Information found at Tag '"
+ GeoTiffKey.GTModelTypeGeoKey + "'");
}
return key;
}
/**
*
* @throws GeoTiffException
*/
public void getCoordinateSystem() throws GeoTiffException {
if (getGTModelTypeGeoKey() == 1) {
// getModelTypeProjected();
} else if (getGTModelTypeGeoKey() == 2) {
// getModelTypeGeographic();
} else if (getGTModelTypeGeoKey() == 3) {
// getModelTypeGeocentric();
} else {
// user-defined?
}
}
/**
*
* @throws GeoTiffException
*/
public Envelope getBoundingBox() throws GeoTiffException {
TIFFField modelPixelScaleTag = this.tifdir.getField(GeoTiffTag.ModelPixelScaleTag);
double resx = modelPixelScaleTag.getAsDouble(0);
double resy = modelPixelScaleTag.getAsDouble(1);
TIFFField modelTiepointTag = this.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 GeoTiffException(
"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 + image.getWidth() * resx;
double yBottom = yOrigin - image.getHeight() * resy;
double xmin = xOrigin;
double ymin = yBottom;
double xmax = xRight;
double ymax = yOrigin;
Envelope envelope =
GeometryFactory.createEnvelope(xmin, ymin, xmax, ymax, null);
return envelope;
}
/**
*
* @return
*/
public String getHumanReadableCoordinateSystem() {
String ret = "";
if (this.geoKeyDirectoryTag.containsKey(new Integer(
GeoTiffKey.PCSCitationGeoKey))) {
int[] key_entry = (int[]) this.geoKeyDirectoryTag.get(new Integer(
GeoTiffKey.PCSCitationGeoKey));
// check if value of field is located in GeoAsciiParamsTag (34737)
if (key_entry[0] == GeoTiffTag.GeoAsciiParamsTag) {
TIFFField field = this.tifdir
.getField(GeoTiffTag.GeoAsciiParamsTag);
int ascii_length = key_entry[1];
int ascii_start = key_entry[2];
// return the string between the two byte-locations - 1 (the
// last '|')
ret = "Projected CS: "
+ field.getAsString(0).substring(ascii_start,
ascii_length - 1);
} else {
ret = "value of field is NOT located in GeoAsciiParamsTag (34737).";
}
} else {
ret = "<empty>";
}
// GeogCitationGeoKey
return ret;
}
// ***********************************************************************
// various GeoTiffReader methods
// ***********************************************************************
/**
* <p>
* description: the following TIFFKeys count as indicator if a TIFF-File
* carries GeoTIFF information: <br>
* ModelPixelScaleTag = 33550 (SoftDesk) <br>
* ModelTransformationTag = 34264 (JPL Carto Group) <br>
* ModelTiepointTag = 33922 (Intergraph) <br>
* GeoKeyDirectoryTag = 34735 (SPOT) <br>
* GeoDoubleParamsTag = 34736 (SPOT) <br>
* GeoAsciiParamsTag = 34737 (SPOT)
*/
private boolean isGeoTiff(TIFFImage image) {
TIFFDirectory directory =
(TIFFDirectory) image.getProperty("tiff_directory");
if (directory.getField(GeoTiffTag.ModelPixelScaleTag) == null
&& directory.getField(GeoTiffTag.ModelTransformationTag) == null
&& directory.getField(GeoTiffTag.ModelTiepointTag) == null
&& directory.getField(GeoTiffTag.GeoKeyDirectoryTag) == null
&& directory.getField(GeoTiffTag.GeoDoubleParamsTag) == null
&& directory.getField(GeoTiffTag.GeoAsciiParamsTag) == null) {
return false;
}
return true;
}
/**
*
* @return
*/
public TIFFImage getTIFFImage() throws GeoTiffException {
if (this.image != null) {
return this.image;
}
throw new GeoTiffException("no image");
}
/**
*
*/
public String toString() {
String ret = "GeoTIFF Information:\n";
if (hasGeoKeyDirectoryTag) {
// Version Information
try {
ret += " Version: " + getGeoKeyDirectoryVersion() + "\n";
ret += " Key_Revision: " + getKeyRevision() + "\n";
ret += " Number Of Keys in GeoKeyDirectoryTag: "
+ getNumberOfKeysInGeoKeyDirectoryTag() + "\n";
ret += " GTModelTypeGeoKey: " + getGTModelTypeGeoKey() + "\n";
} catch (GeoTiffException e) {
ret += "GeoTiffException occured when requesting GeoTIFF Version Information:\n"
+ e.getMessage();
}
ret += "\n";
ret += "Coordinate System (human readable): "
+ getHumanReadableCoordinateSystem() + "\n";
ret += "\n";
} else {
ret += "\nNo GeoKeyDirectoryTag (34735) specified.\n";
}
if (this.tifdir.getField(GeoTiffTag.ModelPixelScaleTag) != null
|| this.tifdir.getField(GeoTiffTag.ModelTiepointTag) != null) {
ret += "Corner Coordinates:\n";
try {
Envelope envelope = getBoundingBox();
ret += " Upper Left ( " + envelope.getMin().getX() + ", "
+ envelope.getMax().getY() + " )\n" + " Lower Left ( "
+ envelope.getMin().getX() + ", "
+ envelope.getMin().getY() + " )\n"
+ " Upper Right ( " + envelope.getMax().getX() + ", "
+ envelope.getMax().getY() + " )\n"
+ " Lower Right ( " + envelope.getMax().getX() + ", "
+ envelope.getMin().getY() + " )\n";
} catch (GeoTiffException e) {
ret += "GeoTiffException occured when calculation the BoundingBox:\n"
+ e.getMessage();
}
} else {
ret += "\nNo BoundingBox Information in ModelPixelScaleTag (33550) and ModelTiepointTag (33922).\n"
+ "ModelTransformationTag (34264) not implemented. \n"
+ "Here is a list of the available tags:\n";
for (int i = 0; i < this.tifdir.getFields().length; i++) {
ret += " tag: " + this.tifdir.getFields()[i].getTag()
+ " \t type: " + this.tifdir.getFields()[i].getType()
+ " \t count: " + this.tifdir.getFields()[i].getCount()
+ "\n";
}
}
return ret;
}
// public static void main(String[] args) throws Exception {
// GeoTiffReader tr = new GeoTiffReader( new File("C:/temp/geotiff/mlatlon.tif") );
// tr.getBoundingBox();
// }
}
/*
* ****************************************************************************
* Changes to this class. What the people have been up to:
*
* $Log: GeoTiffReader.java,v $
* Revision 1.7 2006/08/06 20:56:20 poth
* never read parameter removed
*
* Revision 1.6 2006/04/06 20:25:29 poth
* *** empty log message ***
*
* Revision 1.5 2006/04/04 20:39:43 poth
* *** empty log message ***
*
* Revision 1.4 2006/03/30 21:20:27 poth
* *** empty log message ***
*
* Revision 1.3 2006/01/25 10:34:16 poth
* *** empty log message ***
*
* Revision 1.2 2005/12/06 13:45:20 poth
* System.out.println substituted by logging api
*
* Revision 1.1.1.1 2005/01/05 10:36:35 poth
* no message
*
* Revision 1.2 2004/08/24 07:31:33 ap
* no message
*
* Revision 1.1 2004/07/16 07:03:39 ap
* no message
*
* Revision 1.3 2004/07/15 15:33:43 axel_schaefer
* no message
*
* Revision 1.2 2004/07/15 09:57:23 axel_schaefer
* no message
*
* ****************************************************************************
*/