001    // $HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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    }