001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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 }