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