    001    // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/model/coverage/grid/GeoTIFFGridCoverageReader.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.model.coverage.grid;
    037
    038    import java.awt.Rectangle;
    039    import java.awt.image.Raster;
    040    import java.io.IOException;
    041    import java.io.InputStream;
    042    import java.net.URL;
    043
    044    import javax.media.jai.JAI;
    045    import javax.media.jai.RenderedOp;
    046
    047    import org.deegree.datatypes.CodeList;
    048    import org.deegree.datatypes.parameter.GeneralParameterValueIm;
    049    import org.deegree.datatypes.parameter.InvalidParameterNameException;
    050    import org.deegree.datatypes.parameter.InvalidParameterValueException;
    051    import org.deegree.datatypes.parameter.ParameterNotFoundException;
    052    import org.deegree.framework.log.ILogger;
    053    import org.deegree.framework.log.LoggerFactory;
    054    import org.deegree.framework.util.StringTools;
    055    import org.deegree.graphics.transformation.GeoTransform;
    056    import org.deegree.graphics.transformation.WorldToScreenTransform;
    057    import org.deegree.i18n.Messages;
    058    import org.deegree.model.crs.GeoTransformer;
    059    import org.deegree.model.spatialschema.Envelope;
    060    import org.deegree.model.spatialschema.GeometryFactory;
    061    import org.deegree.ogcwebservices.LonLatEnvelope;
    062    import org.deegree.ogcwebservices.wcs.configuration.File;
    063    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
    064
    065    import com.sun.media.jai.codec.MemoryCacheSeekableStream;
    066    import com.sun.media.jai.codec.SeekableStream;
    067
    068    /**
    069     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
    070     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
    071     *
    072     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    073     * @author last edited by: $Author: mschneider $
    074     *
    075     * @version $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
    076     */
    077    public class GeoTIFFGridCoverageReader extends AbstractGridCoverageReader {
    078
    079        private static final ILogger LOG = LoggerFactory.getLogger( GeoTIFFGridCoverageReader.class );
    080
    081        private SeekableStream sst = null;
    082
    083        /**
    084         * @param source
    085         *            source file of the coverage
    086         * @param description
    087         *            description of the data contained in the source file
    088         * @param envelope
    089         *            desired envelope of the coverage to be read
    090         * @param format
    091         *            image format of the source file
    092         */
    093        public GeoTIFFGridCoverageReader( File source, CoverageOffering description, Envelope envelope, Format format ) {
    094            super( source, description, envelope, format );
    095        }
    096
    097        /**
    098         * @param source
    099         * @param description
    100         *            description of the data contained in the source file
    101         * @param envelope
    102         *            desired envelope of the coverage to be read
    103         * @param format
    104         *            image format of the source file
    105         */
    106        public GeoTIFFGridCoverageReader( InputStream source, CoverageOffering description, Envelope envelope, Format format ) {
    107            super( source, description, envelope, format );
    108        }
    109
    110        /**
    111         * Read the grid coverage from the current stream position, and move to the next grid coverage.
    112         *
    113         * @param parameters
    114         *            An optional set of parameters. Should be any or all of the parameters returned by
    115         *            {@link "org.opengis.coverage.grid.Format#getReadParameters"}.
    116         * @return A new {@linkplain GridCoverage grid coverage} from the input source.
    117         * @throws InvalidParameterNameException
    118         *             if a parameter in <code>parameters</code> doesn't have a recognized name.
    119         * @throws InvalidParameterValueException
    120         *             if a parameter in <code>parameters</code> doesn't have a valid value.
    121         * @throws ParameterNotFoundException
    122         *             if a parameter was required for the operation but was not provided in the
    123         *             <code>parameters</code> list.
    124         * @throws IOException
    125         *             if a read operation failed for some other input/output reason, including
    126         *             {@link java.io.FileNotFoundException} if no file with the given <code>name</code>
    127         *             can be found, or {@link javax.imageio.IIOException} if an error was thrown by the
    128         *             underlying image library.
    129         */
    130        public GridCoverage read( GeneralParameterValueIm[] parameters )
    131                                throws InvalidParameterNameException, InvalidParameterValueException,
    132                                ParameterNotFoundException, IOException {
    133
    134            RenderedOp rop = readGeoTIFF();
    135            int w = rop.getWidth();
    136            int h = rop.getHeight();
    137
    138            // get image rectangle of interrest, envelope and lonlatenvelope
    139            Object[] o = getRasterRegion( w, h );
    140            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    141                LOG.logDebug( "image rectangle of interrest, envelope and lonlatenvelope:" );
    142                for ( int i = 0; i < o.length; i++ ) {
    143                    LOG.logDebug( o[i].toString() );
    144                }
    145            }
    146            Rectangle rect = (Rectangle) o[0];
    147            // return null if the result GC would have a width or height of zero
    148            if ( rect.width == 0 || rect.height == 0 ) {
    149                return null;
    150            }
    151            // create a coverage description that matches the sub image (coverage)
    152            // for this a new LonLatEnvelope must be set
    153            CoverageOffering co = (CoverageOffering) description.clone();
    154            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
    155
    156            // extract required area from the tiff data
    157            Raster raster = rop.getData( rect );
    158            // use 8 BIT as default assuming a raster contains simple
    159            // data like a Landsat TM Band
    160            int pxSize = 8;
    161            if ( rop.getColorModel() != null ) {
    162                pxSize = rop.getColorModel().getPixelSize();
    163            }
    164
    165            return createGridCoverage( raster, co, (Envelope) o[1], pxSize );
    166
    167        }
    168
    169        /**
    170         * creates an instance of <tt>GridCoverage</tt> from the passed Raster, CoverageOffering and
    171         * Envelope. Depending on the pixel size of the the passed raster different types of
    172         * GirdCoverages will be created. possilbe pixel sized are:
    173         * <ul>
    174         * <li>8
    175         * <li>16
    176         * <li>32
    177         * <li>64
    178         * </ul>
    179         *
    180         * @param raster
    181         * @param co
    182         * @param env
    183         * @param pxSize
    184         * @return the coverage
    185         * @throws InvalidParameterValueException
    186         */
    187        private GridCoverage createGridCoverage( Raster raster, CoverageOffering co, Envelope env, int pxSize )
    188                                throws InvalidParameterValueException {
    189
    190            GridCoverage gc = null;
    191            switch ( pxSize ) {
    192            case 8: {
    193                gc = createByteGridCoverage( raster, co, env );
    194                break;
    195            }
    196            case 16: {
    197                gc = createShortGridCoverage( raster, co, env );
    198                break;
    199            }
    200            case 32:
    201            case 64: {
    202                String s = Messages.getMessage( "GC_NOT_SUPPORTED_PS", pxSize );
    203                throw new InvalidParameterValueException( s, "type", pxSize );
    204            }
    205            default:
    206                String s = Messages.getMessage( "GC_UNKNOWN_PS", pxSize );
    207                throw new InvalidParameterValueException( s, "type", pxSize );
    208            }
    209
    210            return gc;
    211        }
    212
    213        /**
    214         * creates a GridCoverage from the passed Raster. The contains data in
    215         * <tt>DataBuffer.TYPE_BYTE</tt> format so the result GridCoverage is of type
    216         * <tt>ByteGridCoverage </tt>
    217         *
    218         * @param raster
    219         * @param co
    220         * @param env
    221         * @return the coverage
    222         */
    223        private ByteGridCoverage createByteGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
    224
    225            Rectangle rect = raster.getBounds();
    226            int bands = raster.getNumBands();
    227            byte[] data = (byte[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
    228            byte[][][] mat = new byte[bands][rect.height][rect.width];
    229            int k = 0;
    230            for ( int i = 0; i < mat[0].length; i++ ) {
    231                for ( int j = 0; j < mat[0][i].length; j++ ) {
    232                    for ( int b = 0; b < bands; b++ ) {
    233                        mat[b][i][j] = data[k++];
    234                    }
    235                }
    236            }
    237
    238            return new ByteGridCoverage( co, env, mat );
    239        }
    240
    241        /**
    242         * creates a GridCoverage from the passed Raster. The contains data in
    243         * <tt>DataBuffer.TYPE_SHORT</tt> format so the result GridCoverage is of type
    244         * <tt>ShortGridCoverage </tt>
    245         *
    246         * @param raster
    247         * @param co
    248         * @param env
    249         * @return the coverage
    250         */
    251        private ShortGridCoverage createShortGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
    252
    253            Rectangle rect = raster.getBounds();
    254            int bands = raster.getNumBands();
    255            short[] data = (short[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
    256            short[][][] mat = new short[bands][rect.height][rect.width];
    257            int k = 0;
    258            for ( int i = 0; i < mat[0].length; i++ ) {
    259                for ( int j = 0; j < mat[0][i].length; j++ ) {
    260                    for ( int b = 0; b < bands; b++ ) {
    261                        mat[b][i][j] = data[k++];
    262                    }
    263                }
    264            }
    265
    266            return new ShortGridCoverage( co, env, mat );
    267        }
    268
    269        /**
    270         * reads an image from its source
    271         *
    272         * @return the image
    273         * @throws IOException
    274         */
    275        private RenderedOp readGeoTIFF()
    276                                throws IOException {
    277
    278            RenderedOp ro = null;
    279            if ( source.getClass() == File.class ) {
    280                String s = ( (File) source ).getName();
    281                String tmp = s.toLowerCase();
    282                LOG.logDebug( "load: ", tmp );
    283                URL url = null;
    284                if ( tmp.startsWith( "file:" ) ) {
    285                    tmp = s.substring( 6, s.length() );
    286                    url = new java.io.File( tmp ).toURL();
    287                } else if ( tmp.startsWith( "http:" ) ) {
    288                    url = new URL( s );
    289                } else {
    290                    url = new java.io.File( s ).toURL();
    291                }
    292                sst = new MemoryCacheSeekableStream( url.openStream() );
    293                ro = JAI.create( "stream", sst );
    294            } else {
    295                sst = new MemoryCacheSeekableStream( (InputStream) source );
    296                ro = JAI.create( "stream", sst );
    297            }
    298
    299            return ro;
    300        }
    301
    302        /**
    303         * returns the region of the source image that intersects with the GridCoverage to be created as
    304         * Rectange as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
    305         * this region.
    306         *
    307         * @param width
    308         *            width of the source image
    309         * @param height
    310         *            height of the source image
    311         * @return the region
    312         */
    313        private Object[] getRasterRegion( int width, int height ) {
    314
    315            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
    316            String code = cl[0].getCodes()[0];
    317
    318            LonLatEnvelope lle = description.getLonLatEnvelope();
    319            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
    320                                                           lle.getMax().getY(), null );
    321            try {
    322                // transform if native CRS is <> EPSG:4326
    323                if ( !( code.equals( "EPSG:4326" ) ) ) {
    324                    GeoTransformer trans = new GeoTransformer( code );
    325                    tmp = trans.transform( tmp, "EPSG:4326" );
    326                }
    327            } catch ( Exception e ) {
    328                LOG.logError( StringTools.stackTraceToString( e ) );
    329            }
    330            // creat tranform object to calculate raster coordinates from
    331            // geo coordinates
    332            GeoTransform gt = new WorldToScreenTransform( tmp.getMin().getX(), tmp.getMin().getY(), tmp.getMax().getX(),
    333                                                          tmp.getMax().getY(), 0, 0, width - 1, height - 1 );
    334
    335            // calculate envelope of the part of the grid coverage that is contained
    336            // within the image
    337            Envelope env = envelope.createIntersection( tmp );
    338
    339            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( env, code );
    340            // calc image coordinates matching the area that is requested
    341            int minx = (int) Math.round( gt.getDestX( env.getMin().getX() ) );
    342            int miny = (int) Math.round( gt.getDestY( env.getMax().getY() ) );
    343            int maxx = (int) Math.round( gt.getDestX( env.getMax().getX() ) );
    344            int maxy = (int) Math.round( gt.getDestY( env.getMin().getY() ) );
    345            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
    346
    347            return new Object[] { rect, env, lonLatEnvelope };
    348        }
    349
    350        /**
    351         * Allows any resources held by this object to be released. The result of calling any other
    352         * method subsequent to a call to this method is undefined. It is important for applications to
    353         * call this method when they know they will no longer be using this
    354         * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to
    355         * resources indefinitely.
    356         *
    357         * @throws IOException
    358         *             if an error occured while disposing resources (for example while closing a file).
    359         */
    360        public void dispose()
    361                                throws IOException {
    362            if ( sst != null ) {
    363                sst.close();
    364            }
    365        }
    366
    367    }