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 }