001 //$HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/io/geotiff/GeoTiffReader.java $ 002 /*---------------------------------------------------------------------------- 003 This file is part of deegree, http://deegree.org/ 004 Copyright (C) 2001-2009 by: 005 Department of Geography, University of Bonn 006 and 007 lat/lon GmbH 008 009 This library is free software; you can redistribute it and/or modify it under 010 the terms of the GNU Lesser General Public License as published by the Free 011 Software Foundation; either version 2.1 of the License, or (at your option) 012 any later version. 013 This library is distributed in the hope that it will be useful, but WITHOUT 014 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 015 FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more 016 details. 017 You should have received a copy of the GNU Lesser General Public License 018 along with this library; if not, write to the Free Software Foundation, Inc., 019 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 020 021 Contact information: 022 023 lat/lon GmbH 024 Aennchenstr. 19, 53177 Bonn 025 Germany 026 http://lat-lon.de/ 027 028 Department of Geography, University of Bonn 029 Prof. Dr. Klaus Greve 030 Postfach 1147, 53001 Bonn 031 Germany 032 http://www.geographie.uni-bonn.de/deegree/ 033 034 e-mail: info@deegree.org 035 ----------------------------------------------------------------------------*/ 036 package org.deegree.io.geotiff; 037 038 import java.io.File; 039 import java.io.FileInputStream; 040 import java.io.FileNotFoundException; 041 import java.io.IOException; 042 import java.util.HashMap; 043 import java.util.StringTokenizer; 044 045 import org.apache.batik.ext.awt.image.codec.FileCacheSeekableStream; 046 import org.apache.batik.ext.awt.image.codec.tiff.TIFFDecodeParam; 047 import org.apache.batik.ext.awt.image.codec.tiff.TIFFDirectory; 048 import org.apache.batik.ext.awt.image.codec.tiff.TIFFField; 049 import org.apache.batik.ext.awt.image.codec.tiff.TIFFImage; 050 import org.deegree.framework.log.ILogger; 051 import org.deegree.framework.log.LoggerFactory; 052 import org.deegree.model.spatialschema.Envelope; 053 import org.deegree.model.spatialschema.GeometryFactory; 054 055 /** 056 * <p> 057 * <tt> 058 * TIFF type :: Java type<br> 059 * TIFF_BYTE :: byte<br> 060 * TIFF_ASCII :: String<br> 061 * TIFF_SHORT :: char<br> 062 * TIFF_LONG :: long<br> 063 * TIFF_RATIONAL :: long[2]<br> 064 * TIFF_SBYTE :: byte<br> 065 * TIFF_UNDEFINED :: byte<br> 066 * TIFF_SSHORT :: short<br> 067 * TIFF_SLONG :: int<br> 068 * TIFF_SRATIONAL :: int[2]<br> 069 * TIFF_FLOAT :: float<br> 070 * TIFF_DOUBLE :: double<br> 071 * </tt> 072 * <p> 073 * 074 * @author <a href="mailto:schaefer@lat-lon.de">Axel Schaefer </A> 075 * @author last edited by: $Author: mschneider $ 076 * @version 2.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18 Jun 2009) $ 077 * @since 078 */ 079 public class GeoTiffReader { 080 081 private static final ILogger LOG = LoggerFactory.getLogger( GeoTiffReader.class ); 082 083 TIFFImage image = null; 084 085 TIFFDirectory tifdir = null; 086 087 HashMap<Integer, int[]> geoKeyDirectoryTag = null; 088 089 boolean hasGeoKeyDirectoryTag = false; 090 091 /** 092 * @param file 093 * @throws FileNotFoundException 094 * @throws IOException 095 * @throws GeoTiffException 096 */ 097 public GeoTiffReader( File file ) throws FileNotFoundException, IOException, GeoTiffException { 098 099 TIFFDecodeParam decodeParam = new TIFFDecodeParam(); 100 int geodirectory = 0; 101 102 FileInputStream fis = new FileInputStream( file ); 103 FileCacheSeekableStream fcss = new FileCacheSeekableStream( fis ); 104 this.image = new TIFFImage( fcss, decodeParam, geodirectory ); 105 106 if ( !isGeoTiff( this.image ) ) { 107 throw new GeoTiffException( "GeoTiffReader: TIFF is no GeoTIFF image!" ); 108 } 109 110 this.tifdir = (TIFFDirectory) image.getProperty( "tiff_directory" ); 111 112 if ( this.tifdir.getField( GeoTiffTag.GeoKeyDirectoryTag ) != null ) { 113 setGeoKeyDirectoryTag(); 114 } 115 fcss.close(); 116 117 } 118 119 // *********************************************************************** 120 // General GeoTIFF tags 121 // *********************************************************************** 122 123 /** 124 * <p> 125 * GeoKeyDirectoryTag: <br> 126 * Tag = 34735 (87AF.H) <br> 127 * Type = SHORT (2-byte unsigned short) <br> 128 * N = variable, >= 4 <br> 129 * Alias: ProjectionInfoTag, CoordSystemInfoTag <br> 130 * Owner: SPOT Image, Inc. 131 * <p> 132 * This tag may be used to store the GeoKey Directory, which defines and references the 133 * "GeoKeys", as described below. 134 * <p> 135 * The tag is an array of unsigned SHORT values, which are primarily grouped into blocks of 4. 136 * The first 4 values are special, and contain GeoKey directory header information. The header 137 * values consist of the following information, in order: 138 * <p> 139 * <tt>Header={KeyDirectoryVersion, KeyRevision, MinorRevision, NumberOfKeys}</tt> 140 * <p> 141 * and as Keys: 142 * <p> 143 * <tt>KeyEntry = { KeyID, TIFFTagLocation, Count, Value_Offset }</tt>^ 144 * <p> 145 * where 146 * <ul> 147 * <li>"KeyID" gives the key-ID value of the Key (identical in function to TIFF tag ID, but 148 * completely independent of TIFF tag-space), 149 * <li>"TIFFTagLocation" indicates which TIFF tag contains the value(s) of the Key: if 150 * TIFFTagLocation is 0, then the value is SHORT, and is contained in the "Value_Offset" entry. 151 * Otherwise, the type (format) of the value is implied by the TIFF-Type of the tag containing 152 * the value. 153 * <li>"Count" indicates the number of values in this key. 154 * <li>"Value_Offset" Value_Offset indicates the index- offset *into* the TagArray indicated by 155 * TIFFTagLocation, if it is nonzero. If TIFFTagLocation=0, then Value_Offset contains the 156 * actual (SHORT) value of the Key, and Count=1 is implied. Note that the offset is not a 157 * byte-offset, but rather an index based on the natural data type of the specified tag array.</li> 158 * </ul> 159 */ 160 private void setGeoKeyDirectoryTag() { 161 TIFFField ff = this.tifdir.getField( GeoTiffTag.GeoKeyDirectoryTag ); 162 163 char[] ch = ff.getAsChars(); 164 165 // resulting HashMap, containing the key and the array of values 166 this.geoKeyDirectoryTag = new HashMap<Integer, int[]>( ff.getCount() / 4 ); 167 // array of values. size is 4-1. 168 169 int keydirversion, keyrevision, minorrevision, numberofkeys = -99; 170 171 for ( int i = 0; i < ch.length; i = i + 4 ) { 172 int[] keys = new int[3]; 173 keydirversion = ch[i]; 174 175 keyrevision = ch[i + 1]; 176 minorrevision = ch[i + 2]; 177 numberofkeys = ch[i + 3]; 178 keys[0] = keyrevision; 179 keys[1] = minorrevision; 180 keys[2] = numberofkeys; 181 182 LOG.logDebug( "[" + i + "].KEY: " + keydirversion + " \t" + keyrevision + "\t" + minorrevision + "\t" 183 + numberofkeys ); 184 this.geoKeyDirectoryTag.put( new Integer( keydirversion ), keys ); 185 } 186 this.hasGeoKeyDirectoryTag = true; 187 } 188 189 /** 190 * <p> 191 * GeoDoubleParamsTag: <br> 192 * Tag = 34736 (87BO.H) <br> 193 * Type = DOUBLE (IEEE Double precision) <br> 194 * N = variable <br> 195 * Owner: SPOT Image, Inc. 196 * <p> 197 * This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the 198 * GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the 199 * GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE 200 * and stored here. 201 * 202 * @return null 203 */ 204 public Object getGeoDoubleParamsTag() { 205 // TIFFField ff = this.tifdir.getField(GeoTiffTag.GeoDoubleParamsTag); 206 // TODO GeoDoubleParamsTag 207 return null; 208 } 209 210 /** 211 * <p> 212 * GeoAsciiParamsTag: <br> 213 * Tag = 34737 (87B1.H) <br> 214 * Type = ASCII <br> 215 * Owner: SPOT Image, Inc. <br> 216 * N = variable 217 * <p> 218 * This tag is used to store all of the DOUBLE valued GeoKeys, referenced by the 219 * GeoKeyDirectoryTag. The meaning of any value of this double array is determined from the 220 * GeoKeyDirectoryTag reference pointing to it. FLOAT values should first be converted to DOUBLE 221 * and stored here. 222 * <p> 223 * A baseline GeoTIFF-reader must check for and convert the final "|" pipe character of a key 224 * back into a NULL before returning it to the client software. 225 * 226 * @return the fields 227 * 228 */ 229 public String[] getGeoAsciiParamsTag() { 230 231 // TODO: getGeoAsciiParamsTag(int count, int value_offset)!!! 232 233 TIFFField field = this.tifdir.getField( GeoTiffTag.GeoAsciiParamsTag ); 234 String gapt = field.getAsString( 0 ); 235 236 LOG.logDebug( gapt ); 237 238 StringTokenizer st = new StringTokenizer( gapt, "|" ); 239 240 LOG.logDebug( "countTokens: " + st.countTokens() ); 241 242 String[] gapt_fields = new String[st.countTokens()]; 243 int i = 0; 244 while ( st.hasMoreTokens() ) { 245 gapt_fields[i++] = st.nextToken(); 246 } 247 248 for ( int j = 0; j < gapt_fields.length; j++ ) { 249 LOG.logDebug( gapt_fields[j] ); 250 } 251 252 return gapt_fields; 253 } 254 255 // *********************************************************************** 256 // specific GeoTIFF contents GeoTIFF keys 257 // *********************************************************************** 258 /** 259 * 260 */ 261 private int[] getVersionInformation() 262 throws GeoTiffException { 263 264 int[] content = new int[3]; 265 if ( this.geoKeyDirectoryTag.containsKey( new Integer( 1 ) ) ) { 266 content = this.geoKeyDirectoryTag.get( new Integer( 1 ) ); 267 } else { 268 throw new GeoTiffException( "No GeoTIFF Information found at Tag '1'" ); 269 } 270 return content; 271 } 272 273 /** 274 * 275 * @return fixed 1 276 * @throws GeoTiffException 277 */ 278 public int getGeoKeyDirectoryVersion() 279 throws GeoTiffException { 280 getVersionInformation(); 281 return 1; 282 } 283 284 /** 285 * 286 * @return the rev 287 * @throws GeoTiffException 288 */ 289 public String getKeyRevision() 290 throws GeoTiffException { 291 String key_revision = ""; 292 int[] kv = getVersionInformation(); 293 key_revision = kv[0] + "." + kv[1]; 294 return key_revision; 295 } 296 297 /** 298 * @return the number 299 * @throws GeoTiffException 300 */ 301 public int getNumberOfKeysInGeoKeyDirectoryTag() 302 throws GeoTiffException { 303 int[] kv = getVersionInformation(); 304 return kv[2]; 305 } 306 307 /** 308 * <p> 309 * Key ID = 1024 <br> 310 * Type: SHORT (code) <br> 311 * <p> 312 * This GeoKey defines the general type of model Coordinate system used, and to which the raster 313 * space will be transformed:unknown, Geocentric (rarely used), Geographic, Projected Coordinate 314 * System, or user-defined. If the coordinate system is a PCS, then only the PCS code need be 315 * specified. If the coordinate system does not fit into one of the standard registered PCS'S, 316 * but it uses one of the standard projections and datums, then its should be documented as a 317 * PCS model with "user-defined" type, requiring the specification of projection parameters, 318 * etc. 319 * <p> 320 * GeoKey requirements for User-Defined Model Type (not advisable): GTCitationGeoKey 321 * 322 * @return (0) unknown, <br> 323 * (1) ModelTypeProjected (Projection Coordinate System), <br> 324 * (2) ModelTypeGeographic (Geographic latitude-longitude System), <br> 325 * (3) ModelTypeGeocentric (Geocentric (X,Y,Z) Coordinate System) (rarely used), <br> 326 * (4?) user-defined 327 * 328 * @throws GeoTiffException 329 */ 330 public int getGTModelTypeGeoKey() 331 throws GeoTiffException { 332 int[] content = new int[3]; 333 int key = -99; 334 335 if ( this.geoKeyDirectoryTag.containsKey( new Integer( GeoTiffKey.GTModelTypeGeoKey ) ) ) { 336 content = this.geoKeyDirectoryTag.get( new Integer( GeoTiffKey.GTModelTypeGeoKey ) ); 337 338 // TIFFTagLocation 339 if ( content[0] == 0 ) { 340 // return Value_Offset 341 key = content[2]; 342 } else { 343 // TODO other TIFFTagLocation that GeoKeyDirectoryTag 344 } 345 } else { 346 throw new GeoTiffException( "No GeoTIFF Information found at Tag '" + GeoTiffKey.GTModelTypeGeoKey + "'" ); 347 } 348 return key; 349 } 350 351 /** 352 * 353 * @throws GeoTiffException 354 */ 355 public void getCoordinateSystem() 356 throws GeoTiffException { 357 358 if ( getGTModelTypeGeoKey() == 1 ) { 359 // getModelTypeProjected(); 360 } else if ( getGTModelTypeGeoKey() == 2 ) { 361 // getModelTypeGeographic(); 362 } else if ( getGTModelTypeGeoKey() == 3 ) { 363 // getModelTypeGeocentric(); 364 } else { 365 // user-defined? 366 } 367 368 } 369 370 /** 371 * 372 * @return the bbox 373 * @throws GeoTiffException 374 */ 375 public Envelope getBoundingBox() 376 throws GeoTiffException { 377 378 TIFFField modelPixelScaleTag = this.tifdir.getField( GeoTiffTag.ModelPixelScaleTag ); 379 double resx = modelPixelScaleTag.getAsDouble( 0 ); 380 double resy = modelPixelScaleTag.getAsDouble( 1 ); 381 382 TIFFField modelTiepointTag = this.tifdir.getField( GeoTiffTag.ModelTiepointTag ); 383 double val1 = 0.0; 384 val1 = modelTiepointTag.getAsDouble( 0 ); 385 double val2 = 0.0; 386 val2 = modelTiepointTag.getAsDouble( 1 ); 387 double val4 = 0.0; 388 val4 = modelTiepointTag.getAsDouble( 3 ); 389 double val5 = 0.0; 390 val5 = modelTiepointTag.getAsDouble( 4 ); 391 392 if ( ( resx == 0.0 || resy == 0.0 ) || ( val1 == 0.0 && val2 == 0.0 && val4 == 0.0 && val5 == 0.0 ) ) { 393 throw new GeoTiffException( "The image/coverage hasn't a bounding box" ); 394 // set the geoparams derived by geoTiffTags 395 } 396 397 // upper/left pixel 398 double xOrigin = val4 - ( val1 * resx ); 399 double yOrigin = val5 - ( val2 * resy ); 400 401 // lower/right pixel 402 double xRight = xOrigin + image.getWidth() * resx; 403 double yBottom = yOrigin - image.getHeight() * resy; 404 405 double xmin = xOrigin; 406 double ymin = yBottom; 407 double xmax = xRight; 408 double ymax = yOrigin; 409 410 Envelope envelope = GeometryFactory.createEnvelope( xmin, ymin, xmax, ymax, null ); 411 412 return envelope; 413 } 414 415 /** 416 * 417 * @return the crs 418 */ 419 public String getHumanReadableCoordinateSystem() { 420 421 String ret = ""; 422 423 if ( this.geoKeyDirectoryTag.containsKey( new Integer( GeoTiffKey.PCSCitationGeoKey ) ) ) { 424 425 int[] key_entry = this.geoKeyDirectoryTag.get( new Integer( GeoTiffKey.PCSCitationGeoKey ) ); 426 427 // check if value of field is located in GeoAsciiParamsTag (34737) 428 if ( key_entry[0] == GeoTiffTag.GeoAsciiParamsTag ) { 429 TIFFField field = this.tifdir.getField( GeoTiffTag.GeoAsciiParamsTag ); 430 431 int ascii_length = key_entry[1]; 432 int ascii_start = key_entry[2]; 433 434 // return the string between the two byte-locations - 1 (the 435 // last '|') 436 ret = "Projected CS: " + field.getAsString( 0 ).substring( ascii_start, ascii_length - 1 ); 437 438 } else { 439 ret = "value of field is NOT located in GeoAsciiParamsTag (34737)."; 440 } 441 } else { 442 ret = "<empty>"; 443 } 444 445 // GeogCitationGeoKey 446 447 return ret; 448 } 449 450 // *********************************************************************** 451 // various GeoTiffReader methods 452 // *********************************************************************** 453 454 /** 455 * <p> 456 * description: the following TIFFKeys count as indicator if a TIFF-File carries GeoTIFF 457 * information: <br> 458 * ModelPixelScaleTag = 33550 (SoftDesk) <br> 459 * ModelTransformationTag = 34264 (JPL Carto Group) <br> 460 * ModelTiepointTag = 33922 (Intergraph) <br> 461 * GeoKeyDirectoryTag = 34735 (SPOT) <br> 462 * GeoDoubleParamsTag = 34736 (SPOT) <br> 463 * GeoAsciiParamsTag = 34737 (SPOT) 464 */ 465 private boolean isGeoTiff( TIFFImage image ) { 466 TIFFDirectory directory = (TIFFDirectory) image.getProperty( "tiff_directory" ); 467 468 if ( directory.getField( GeoTiffTag.ModelPixelScaleTag ) == null 469 && directory.getField( GeoTiffTag.ModelTransformationTag ) == null 470 && directory.getField( GeoTiffTag.ModelTiepointTag ) == null 471 && directory.getField( GeoTiffTag.GeoKeyDirectoryTag ) == null 472 && directory.getField( GeoTiffTag.GeoDoubleParamsTag ) == null 473 && directory.getField( GeoTiffTag.GeoAsciiParamsTag ) == null ) { 474 return false; 475 } 476 return true; 477 } 478 479 /** 480 * 481 * @return the image 482 * @throws GeoTiffException 483 */ 484 public TIFFImage getTIFFImage() 485 throws GeoTiffException { 486 if ( this.image != null ) { 487 return this.image; 488 } 489 throw new GeoTiffException( "no image" ); 490 } 491 492 /** 493 * 494 */ 495 @Override 496 public String toString() { 497 String ret = "GeoTIFF Information:\n"; 498 499 if ( hasGeoKeyDirectoryTag ) { 500 501 // Version Information 502 try { 503 ret += " Version: " + getGeoKeyDirectoryVersion() + "\n"; 504 ret += " Key_Revision: " + getKeyRevision() + "\n"; 505 ret += " Number Of Keys in GeoKeyDirectoryTag: " + getNumberOfKeysInGeoKeyDirectoryTag() + "\n"; 506 ret += " GTModelTypeGeoKey: " + getGTModelTypeGeoKey() + "\n"; 507 508 } catch ( GeoTiffException e ) { 509 ret += "GeoTiffException occured when requesting GeoTIFF Version Information:\n" + e.getMessage(); 510 } 511 512 ret += "\n"; 513 ret += "Coordinate System (human readable): " + getHumanReadableCoordinateSystem() + "\n"; 514 ret += "\n"; 515 } else { 516 ret += "\nNo GeoKeyDirectoryTag (34735) specified.\n"; 517 } 518 519 if ( this.tifdir.getField( GeoTiffTag.ModelPixelScaleTag ) != null 520 || this.tifdir.getField( GeoTiffTag.ModelTiepointTag ) != null ) { 521 522 ret += "Corner Coordinates:\n"; 523 try { 524 Envelope envelope = getBoundingBox(); 525 ret += " Upper Left ( " + envelope.getMin().getX() + ", " + envelope.getMax().getY() + " )\n" 526 + " Lower Left ( " + envelope.getMin().getX() + ", " + envelope.getMin().getY() + " )\n" 527 + " Upper Right ( " + envelope.getMax().getX() + ", " + envelope.getMax().getY() + " )\n" 528 + " Lower Right ( " + envelope.getMax().getX() + ", " + envelope.getMin().getY() + " )\n"; 529 } catch ( GeoTiffException e ) { 530 ret += "GeoTiffException occured when calculation the BoundingBox:\n" + e.getMessage(); 531 } 532 } else { 533 ret += "\nNo BoundingBox Information in ModelPixelScaleTag (33550) and ModelTiepointTag (33922).\n" 534 + "ModelTransformationTag (34264) not implemented. \n" + "Here is a list of the available tags:\n"; 535 536 for ( int i = 0; i < this.tifdir.getFields().length; i++ ) { 537 ret += " tag: " + this.tifdir.getFields()[i].getTag() + " \t type: " 538 + this.tifdir.getFields()[i].getType() + " \t count: " + this.tifdir.getFields()[i].getCount() 539 + "\n"; 540 } 541 542 } 543 544 return ret; 545 } 546 547 }