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    }