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