    001    // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/model/coverage/grid/ImageGridCoverageReader.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.BufferedImage;
    040    import java.io.IOException;
    041    import java.io.InputStream;
    042    import java.net.URL;
    043
    044    import org.deegree.datatypes.CodeList;
    045    import org.deegree.datatypes.parameter.GeneralParameterValueIm;
    046    import org.deegree.datatypes.parameter.InvalidParameterNameException;
    047    import org.deegree.datatypes.parameter.InvalidParameterValueException;
    048    import org.deegree.datatypes.parameter.OperationParameterIm;
    049    import org.deegree.datatypes.parameter.ParameterNotFoundException;
    050    import org.deegree.framework.log.ILogger;
    051    import org.deegree.framework.log.LoggerFactory;
    052    import org.deegree.framework.util.ImageUtils;
    053    import org.deegree.framework.util.StringTools;
    054    import org.deegree.graphics.transformation.GeoTransform;
    055    import org.deegree.graphics.transformation.WorldToScreenTransform;
    056    import org.deegree.io.ecwapi.ECWReader;
    057    import org.deegree.model.crs.GeoTransformer;
    058    import org.deegree.model.spatialschema.Envelope;
    059    import org.deegree.model.spatialschema.GeometryFactory;
    060    import org.deegree.ogcwebservices.LonLatEnvelope;
    061    import org.deegree.ogcwebservices.wcs.configuration.File;
    062    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
    063
    064    /**
    065     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
    066     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
    067     *
    068     * @version $Revision: 21400 $
    069     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
    070     * @author last edited by: $Author: apoth $
    071     *
    072     * @version 1.0. $Revision: 21400 $, $Date: 2009-12-11 12:01:34 +0100 (Fr, 11. Dez 2009) $
    073     *
    074     * @since 2.0
    075     */
    076
    077    public class ImageGridCoverageReader extends AbstractGridCoverageReader {
    078
    079        private static final ILogger LOGGER = LoggerFactory.getLogger( ImageGridCoverageReader.class );
    080
    081        /**
    082         * @param source
    083         *            source file of the coverage
    084         * @param description
    085         *            description of the data contained in the source file
    086         * @param envelope
    087         *            desired envelope of the coverage to be read
    088         * @param format
    089         *            image format of the source file
    090         */
    091        public ImageGridCoverageReader( File source, CoverageOffering description, Envelope envelope, Format format ) {
    092            super( source, description, envelope, format );
    093        }
    094
    095        /**
    096         * @param source
    097         * @param description
    098         *            description of the data contained in the source file
    099         * @param envelope
    100         *            desired envelope of the coverage to be read
    101         * @param format
    102         *            image format of the source file
    103         */
    104        public ImageGridCoverageReader( InputStream source, CoverageOffering description, Envelope envelope, Format format ) {
    105            super( source, description, envelope, format );
    106        }
    107
    108        /**
    109         * Read the grid coverage from the current stream position, and move to the next grid coverage.
    110         *
    111         * @param parameters
    112         *            An optional set of parameters. Should be any or all of the parameters returned by
    113         *            {@link "org.opengis.coverage.grid.Format#getReadParameters"}.
    114         * @return A new {@linkplain GridCoverage grid coverage} from the input source.
    115         * @throws InvalidParameterNameException
    116         *             if a parameter in <code>parameters</code> doesn't have a recognized name.
    117         * @throws InvalidParameterValueException
    118         *             if a parameter in <code>parameters</code> doesn't have a valid value.
    119         * @throws ParameterNotFoundException
    120         *             if a parameter was required for the operation but was not provided in the
    121         *             <code>parameters</code> list.
    122         * @throws IOException
    123         *             if a read operation failed for some other input/output reason, including
    124         *             {@link java.io.FileNotFoundException} if no file with the given <code>name</code>
    125         *             can be found, or {@link javax.imageio.IIOException} if an error was thrown by the
    126         *             underlying image library.
    127         */
    128        public GridCoverage read( GeneralParameterValueIm[] parameters )
    129                                throws InvalidParameterNameException, InvalidParameterValueException,
    130                                ParameterNotFoundException, IOException {
    131
    132            String frmt = description.getSupportedFormats().getNativeFormat().getCode();
    133            GridCoverage gc = null;
    134            if ( frmt.equalsIgnoreCase( "ecw" ) ) {
    135                gc = performECW( parameters );
    136            } else if ( frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" ) || frmt.equalsIgnoreCase( "tif" )
    137                        || frmt.equalsIgnoreCase( "tiff" ) || frmt.equalsIgnoreCase( "gif" )
    138                        || frmt.equalsIgnoreCase( "jpg" ) || frmt.equalsIgnoreCase( "jpeg" ) ) {
    139                gc = performImage();
    140            } else {
    141                throw new InvalidParameterValueException( "unknown format", "native format", format );
    142            }
    143
    144            return gc;
    145        }
    146
    147        /**
    148         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
    149         * reader.
    150         *
    151         * @param parameters
    152         * @throws IOException
    153         */
    154        private GridCoverage performECW( GeneralParameterValueIm[] parameters )
    155                                throws IOException {
    156
    157            BufferedImage bi = null;
    158            CoverageOffering co = null;
    159            Object[] o = null;
    160
    161            ECWReader ecwFile = null;
    162            try {
    163                String s = ( (File) source ).getName();
    164                ecwFile = new ECWReader( s );
    165
    166                // get the requested dimension in pixels
    167                int reqWidth = 0;
    168                int reqHeight = 0;
    169                for ( int i = 0; i < parameters.length; i++ ) {
    170                    OperationParameterIm op = (OperationParameterIm) parameters[i].getDescriptor();
    171                    String name = op.getName();
    172                    if ( name.equalsIgnoreCase( "WIDTH" ) ) {
    173                        Object vo = op.getDefaultValue();
    174                        reqWidth = ( (Integer) vo ).intValue();
    175                    } else if ( name.equalsIgnoreCase( "HEIGHT" ) ) {
    176                        Object vo = op.getDefaultValue();
    177                        reqHeight = ( (Integer) vo ).intValue();
    178                    }
    179                }
    180
    181                // calculate image region of interest
    182                o = getECWImageRegion( reqWidth, reqHeight );
    183                Envelope envl = (Envelope) o[1];
    184
    185                Rectangle rect = (Rectangle) o[0];
    186                bi = ecwFile.getBufferedImage( envl, rect.width, rect.height );
    187
    188                // create a coverage description that matches the sub image (coverage)
    189                // for this a new LonLatEnvelope must be set
    190                co = (CoverageOffering) description.clone();
    191                co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
    192
    193            } catch ( Exception e ) {
    194                e.printStackTrace();
    195                throw new IOException( StringTools.stackTraceToString( e ) );
    196            } finally {
    197                // free the ECW cache memory
    198                if ( ecwFile != null ) {
    199                    ecwFile.close();
    200                }
    201            }
    202
    203            return new ImageGridCoverage( co, (Envelope) o[1], bi );
    204
    205        }
    206
    207        /**
    208         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
    209         * reader.
    210         *
    211         * @throws IOException
    212         * @throws ParameterNotFoundException
    213         */
    214        private GridCoverage performImage()
    215                                throws ParameterNotFoundException, IOException {
    216
    217            BufferedImage bi = readImage();
    218
    219            // get image rectangle of interrest, envelope and lonlatenvelope
    220            Object[] o = getImageRegion( bi.getWidth(), bi.getHeight() );
    221            Rectangle rect = (Rectangle) o[0];
    222            // return null if the result GC would have a width or height of zero
    223            if ( rect.width == 0 || rect.height == 0 ) {
    224                return null;
    225            }
    226            bi = bi.getSubimage( rect.x, rect.y, rect.width, rect.height );
    227
    228            // create a coverage description that matches the sub image (coverage)
    229            // for this a new LonLatEnvelope must be set
    230            CoverageOffering co = (CoverageOffering) description.clone();
    231            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
    232
    233            return new ImageGridCoverage( co, (Envelope) o[1], bi );
    234        }
    235
    236        /**
    237         * reads an image from its source
    238         *
    239         * @throws IOException
    240         */
    241        private BufferedImage readImage()
    242                                throws IOException {
    243            BufferedImage bi = null;
    244            if ( source.getClass() == File.class ) {
    245                String s = ( (File) source ).getName();
    246                String tmp = s.toLowerCase();
    247                if ( tmp.startsWith( "file:" ) ) {
    248                    tmp = s.substring( 6, s.length() );
    249                    bi = ImageUtils.loadImage( new java.io.File( tmp ) );
    250                } else if ( tmp.startsWith( "http:" ) ) {
    251                    bi = ImageUtils.loadImage( new URL( s ) );
    252                } else {
    253                    bi = ImageUtils.loadImage( new java.io.File( s ) );
    254                }
    255            } else {
    256                bi = ImageUtils.loadImage( (InputStream) source );
    257            }
    258            return bi;
    259        }
    260
    261        /**
    262         * Return the SRS code of our native SRS.
    263         */
    264        private String getNativeSRSCode() {
    265            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
    266            return cl[0].getCodes()[0];
    267        }
    268
    269        /**
    270         * return the LonLatEnvelope of the entire image in "EPSG:4326"
    271         */
    272        private Envelope getLLEAsEnvelope() {
    273            String code = getNativeSRSCode();
    274            LonLatEnvelope lle = description.getLonLatEnvelope();
    275            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
    276                                                           lle.getMax().getY(), null );
    277            try {
    278                if ( !code.equals( "EPSG:4326" ) ) {
    279                    GeoTransformer trans = new GeoTransformer( code );
    280                    tmp = trans.transform( tmp, "EPSG:4326" );
    281                }
    282            } catch ( Exception e ) {
    283                LOGGER.logError( StringTools.stackTraceToString( e ) );
    284            }
    285
    286            return tmp;
    287        }
    288
    289        /**
    290         * Calculate the rectangle that belongs to the given destination envelope in the given source
    291         * image.
    292         */
    293        private Object[] calcRegionRectangle( Envelope dstenv, Envelope srcenv, double srcwidth, double srcheight ) {
    294            GeoTransform gt = new WorldToScreenTransform( srcenv.getMin().getX(), srcenv.getMin().getY(),
    295                                                          srcenv.getMax().getX(), srcenv.getMax().getY(), 0, 0,
    296                                                          srcwidth - 1, srcheight - 1 );
    297
    298            int minx = (int) Math.round( gt.getDestX( dstenv.getMin().getX() ) );
    299            int miny = (int) Math.round( gt.getDestY( dstenv.getMax().getY() ) );
    300            int maxx = (int) Math.round( gt.getDestX( dstenv.getMax().getX() ) );
    301            int maxy = (int) Math.round( gt.getDestY( dstenv.getMin().getY() ) );
    302            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
    303            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( dstenv, getNativeSRSCode() );
    304
    305            return new Object[] { rect, dstenv, lonLatEnvelope };
    306        }
    307
    308        /**
    309         * returns the region of the source image that intersects with the GridCoverage to be created as
    310         * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
    311         * this region.
    312         *
    313         * @param imgwidth
    314         *            width of the source image
    315         * @param imgheight
    316         *            height of the source image
    317         * @return the region of the source image that intersects with the GridCoverage to be created as
    318         *         Rectangle as well as the Envelope of the region in the native CRS and the
    319         *         LonLatEnvelope of this region.
    320         */
    321        private Object[] getImageRegion( int imgwidth, int imgheight ) {
    322
    323            Envelope imgenvelope = getLLEAsEnvelope();
    324            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
    325            return calcRegionRectangle( dstenvelope, imgenvelope, imgwidth, imgheight );
    326        }
    327
    328        /**
    329         * returns the region of the source image that intersects with the GridCoverage to be created as
    330         * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
    331         * this region. For ECW files these values reflect exactly the desired result, as the library
    332         * will cut and sample the source image during read.
    333         *
    334         * @param reqwidth
    335         *            width of the requested region
    336         * @param reqheight
    337         *            height of the requested region
    338         * @return the region of the source image that intersects with the GridCoverage to be created as
    339         *         Rectangle as well as the Envelope of the region in the native CRS and the
    340         *         LonLatEnvelope of this region.
    341         */
    342        private Object[] getECWImageRegion( int reqwidth, int reqheight ) {
    343
    344            Envelope imgenvelope = getLLEAsEnvelope();
    345            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
    346
    347            // In case of ECW files, we calculate the rectangle according
    348            // to the desired result, not according to the source image,
    349            // as clipping and sampling will be done by the ECW library.
    350            // Hence dstenvelope and srcenvelope are the same and width/height
    351            // must be clipped with our grid-cell (if necessary).
    352            double dWidth = ( dstenvelope.getWidth() * reqwidth ) / envelope.getWidth();
    353            double dHeight = ( dstenvelope.getHeight() * reqheight ) / envelope.getHeight();
    354            return calcRegionRectangle( dstenvelope, dstenvelope, dWidth, dHeight );
    355        }
    356
    357        /**
    358         * Allows any resources held by this object to be released. The result of calling any other
    359         * method subsequent to a call to this method is undefined. It is important for applications to
    360         * call this method when they know they will no longer be using this
    361         * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to
    362         * resources indefinitely.
    363         *
    364         * @throws IOException
    365         *             if an error occured while disposing resources (for example while closing a file).
    366         */
    367        public void dispose()
    368                                throws IOException {
    369            if ( source instanceof InputStream ) {
    370                ( (InputStream) source ).close();
    371            }
    372        }
    373
    374    }