001    // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    import com.ermapper.ecw.JNCSException;
065    
066    /**
067     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
068     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
069     *
070     * @version $Revision: 18195 $
071     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
072     * @author last edited by: $Author: mschneider $
073     *
074     * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
075     *
076     * @since 2.0
077     */
078    
079    public class ImageGridCoverageReader extends AbstractGridCoverageReader {
080    
081        private static final ILogger LOGGER = LoggerFactory.getLogger( ImageGridCoverageReader.class );
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 ImageGridCoverageReader( 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 ImageGridCoverageReader( 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            String frmt = description.getSupportedFormats().getNativeFormat().getCode();
135            GridCoverage gc = null;
136            if ( frmt.equalsIgnoreCase( "ecw" ) ) {
137                gc = performECW( parameters );
138            } else if ( frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" ) || frmt.equalsIgnoreCase( "tif" )
139                        || frmt.equalsIgnoreCase( "tiff" ) || frmt.equalsIgnoreCase( "gif" )
140                        || frmt.equalsIgnoreCase( "jpg" ) || frmt.equalsIgnoreCase( "jpeg" ) ) {
141                gc = performImage();
142            } else {
143                throw new InvalidParameterValueException( "unknown format", "native format", format );
144            }
145    
146            return gc;
147        }
148    
149        /**
150         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
151         * reader.
152         *
153         * @param parameters
154         * @throws IOException
155         */
156        private GridCoverage performECW( GeneralParameterValueIm[] parameters )
157                                throws IOException {
158    
159            BufferedImage bi = null;
160            CoverageOffering co = null;
161            Object[] o = null;
162    
163            ECWReader ecwFile = null;
164            try {
165                String s = ( (File) source ).getName();
166                ecwFile = new ECWReader( s );
167    
168                // get the requested dimension in pixels
169                int reqWidth = 0;
170                int reqHeight = 0;
171                for ( int i = 0; i < parameters.length; i++ ) {
172                    OperationParameterIm op = (OperationParameterIm) parameters[i].getDescriptor();
173                    String name = op.getName();
174                    if ( name.equalsIgnoreCase( "WIDTH" ) ) {
175                        Object vo = op.getDefaultValue();
176                        reqWidth = ( (Integer) vo ).intValue();
177                    } else if ( name.equalsIgnoreCase( "HEIGHT" ) ) {
178                        Object vo = op.getDefaultValue();
179                        reqHeight = ( (Integer) vo ).intValue();
180                    }
181                }
182    
183                // calculate image region of interest
184                o = getECWImageRegion( reqWidth, reqHeight );
185                Envelope envl = (Envelope) o[1];
186    
187                Rectangle rect = (Rectangle) o[0];
188                bi = ecwFile.getBufferedImage( envl, rect.width, rect.height );
189    
190                // create a coverage description that matches the sub image (coverage)
191                // for this a new LonLatEnvelope must be set
192                co = (CoverageOffering) description.clone();
193                co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
194    
195            } catch ( JNCSException e ) {
196                throw new IOException( StringTools.stackTraceToString( e ) );
197            } finally {
198                // free the ECW cache memory
199                if ( ecwFile != null ) {
200                    ecwFile.close();
201                }
202            }
203    
204            return new ImageGridCoverage( co, (Envelope) o[1], bi );
205    
206        }
207    
208        /**
209         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
210         * reader.
211         *
212         * @throws IOException
213         * @throws ParameterNotFoundException
214         */
215        private GridCoverage performImage()
216                                throws ParameterNotFoundException, IOException {
217    
218            BufferedImage bi = readImage();
219    
220            // get image rectangle of interrest, envelope and lonlatenvelope
221            Object[] o = getImageRegion( bi.getWidth(), bi.getHeight() );
222            Rectangle rect = (Rectangle) o[0];
223            // return null if the result GC would have a width or height of zero
224            if ( rect.width == 0 || rect.height == 0 ) {
225                return null;
226            }
227            bi = bi.getSubimage( rect.x, rect.y, rect.width, rect.height );
228    
229            // create a coverage description that matches the sub image (coverage)
230            // for this a new LonLatEnvelope must be set
231            CoverageOffering co = (CoverageOffering) description.clone();
232            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
233    
234            return new ImageGridCoverage( co, (Envelope) o[1], bi );
235        }
236    
237        /**
238         * reads an image from its source
239         *
240         * @throws IOException
241         */
242        private BufferedImage readImage()
243                                throws IOException {
244            BufferedImage bi = null;
245            if ( source.getClass() == File.class ) {
246                String s = ( (File) source ).getName();
247                String tmp = s.toLowerCase();
248                if ( tmp.startsWith( "file:" ) ) {
249                    tmp = s.substring( 6, s.length() );
250                    bi = ImageUtils.loadImage( new java.io.File( tmp ) );
251                } else if ( tmp.startsWith( "http:" ) ) {
252                    bi = ImageUtils.loadImage( new URL( s ) );
253                } else {
254                    bi = ImageUtils.loadImage( new java.io.File( s ) );
255                }
256            } else {
257                bi = ImageUtils.loadImage( (InputStream) source );
258            }
259            return bi;
260        }
261    
262        /**
263         * Return the SRS code of our native SRS.
264         */
265        private String getNativeSRSCode() {
266            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
267            return cl[0].getCodes()[0];
268        }
269    
270        /**
271         * return the LonLatEnvelope of the entire image in "EPSG:4326"
272         */
273        private Envelope getLLEAsEnvelope() {
274            String code = getNativeSRSCode();
275            LonLatEnvelope lle = description.getLonLatEnvelope();
276            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
277                                                           lle.getMax().getY(), null );
278            try {
279                if ( !code.equals( "EPSG:4326" ) ) {
280                    GeoTransformer trans = new GeoTransformer( code );
281                    tmp = trans.transform( tmp, "EPSG:4326" );
282                }
283            } catch ( Exception e ) {
284                LOGGER.logError( StringTools.stackTraceToString( e ) );
285            }
286    
287            return tmp;
288        }
289    
290        /**
291         * Calculate the rectangle that belongs to the given destination envelope in the given source
292         * image.
293         */
294        private Object[] calcRegionRectangle( Envelope dstenv, Envelope srcenv, double srcwidth, double srcheight ) {
295            GeoTransform gt = new WorldToScreenTransform( srcenv.getMin().getX(), srcenv.getMin().getY(),
296                                                          srcenv.getMax().getX(), srcenv.getMax().getY(), 0, 0,
297                                                          srcwidth - 1, srcheight - 1 );
298    
299            int minx = (int) Math.round( gt.getDestX( dstenv.getMin().getX() ) );
300            int miny = (int) Math.round( gt.getDestY( dstenv.getMax().getY() ) );
301            int maxx = (int) Math.round( gt.getDestX( dstenv.getMax().getX() ) );
302            int maxy = (int) Math.round( gt.getDestY( dstenv.getMin().getY() ) );
303            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
304            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( dstenv, getNativeSRSCode() );
305    
306            return new Object[] { rect, dstenv, lonLatEnvelope };
307        }
308    
309        /**
310         * returns the region of the source image that intersects with the GridCoverage to be created as
311         * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
312         * this region.
313         *
314         * @param imgwidth
315         *            width of the source image
316         * @param imgheight
317         *            height of the source image
318         * @return the region of the source image that intersects with the GridCoverage to be created as
319         *         Rectangle as well as the Envelope of the region in the native CRS and the
320         *         LonLatEnvelope of this region.
321         */
322        private Object[] getImageRegion( int imgwidth, int imgheight ) {
323    
324            Envelope imgenvelope = getLLEAsEnvelope();
325            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
326            return calcRegionRectangle( dstenvelope, imgenvelope, imgwidth, imgheight );
327        }
328    
329        /**
330         * returns the region of the source image that intersects with the GridCoverage to be created as
331         * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
332         * this region. For ECW files these values reflect exactly the desired result, as the library
333         * will cut and sample the source image during read.
334         *
335         * @param reqwidth
336         *            width of the requested region
337         * @param reqheight
338         *            height of the requested region
339         * @return the region of the source image that intersects with the GridCoverage to be created as
340         *         Rectangle as well as the Envelope of the region in the native CRS and the
341         *         LonLatEnvelope of this region.
342         */
343        private Object[] getECWImageRegion( int reqwidth, int reqheight ) {
344    
345            Envelope imgenvelope = getLLEAsEnvelope();
346            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
347    
348            // In case of ECW files, we calculate the rectangle according
349            // to the desired result, not according to the source image,
350            // as clipping and sampling will be done by the ECW library.
351            // Hence dstenvelope and srcenvelope are the same and width/height
352            // must be clipped with our grid-cell (if necessary).
353            double dWidth = ( dstenvelope.getWidth() * reqwidth ) / envelope.getWidth();
354            double dHeight = ( dstenvelope.getHeight() * reqheight ) / envelope.getHeight();
355            return calcRegionRectangle( dstenvelope, dstenvelope, dWidth, dHeight );
356        }
357    
358        /**
359         * Allows any resources held by this object to be released. The result of calling any other
360         * method subsequent to a call to this method is undefined. It is important for applications to
361         * call this method when they know they will no longer be using this
362         * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to
363         * resources indefinitely.
364         *
365         * @throws IOException
366         *             if an error occured while disposing resources (for example while closing a file).
367         */
368        public void dispose()
369                                throws IOException {
370            if ( source instanceof InputStream ) {
371                ( (InputStream) source ).close();
372            }
373        }
374    
375    }