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    }