001    // $HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/coverage/grid/ImageGridCoverageReader.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2006 by:
006     EXSE, Department of Geography, University of Bonn
007     http://www.giub.uni-bonn.de/deegree/
008     lat/lon GmbH
009     http://www.lat-lon.de
010    
011     This library is free software; you can redistribute it and/or
012     modify it under the terms of the GNU Lesser General Public
013     License as published by the Free Software Foundation; either
014     version 2.1 of the License, or (at your option) any later version.
015    
016     This library is distributed in the hope that it will be useful,
017     but WITHOUT ANY WARRANTY; without even the implied warranty of
018     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
019     Lesser General Public License for more details.
020    
021     You should have received a copy of the GNU Lesser General Public
022     License along with this library; if not, write to the Free Software
023     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
024    
025     Contact:
026    
027     Andreas Poth
028     lat/lon GmbH
029     Aennchenstr. 19
030     53115 Bonn
031     Germany
032     E-Mail: poth@lat-lon.de
033    
034     Prof. Dr. Klaus Greve
035     Department of Geography
036     University of Bonn
037     Meckenheimer Allee 166
038     53115 Bonn
039     Germany
040     E-Mail: greve@giub.uni-bonn.de
041    
042     
043     ---------------------------------------------------------------------------*/
044    package org.deegree.model.coverage.grid;
045    
046    import java.awt.Rectangle;
047    import java.awt.image.BufferedImage;
048    import java.io.IOException;
049    import java.io.InputStream;
050    import java.net.URL;
051    
052    import org.deegree.datatypes.CodeList;
053    import org.deegree.datatypes.parameter.GeneralParameterValueIm;
054    import org.deegree.datatypes.parameter.InvalidParameterNameException;
055    import org.deegree.datatypes.parameter.InvalidParameterValueException;
056    import org.deegree.datatypes.parameter.OperationParameterIm;
057    import org.deegree.datatypes.parameter.ParameterNotFoundException;
058    import org.deegree.framework.log.ILogger;
059    import org.deegree.framework.log.LoggerFactory;
060    import org.deegree.framework.util.ImageUtils;
061    import org.deegree.framework.util.StringTools;
062    import org.deegree.graphics.transformation.GeoTransform;
063    import org.deegree.graphics.transformation.WorldToScreenTransform;
064    import org.deegree.io.ecwapi.ECWReader;
065    import org.deegree.model.crs.GeoTransformer;
066    import org.deegree.model.crs.IGeoTransformer;
067    import org.deegree.model.spatialschema.Envelope;
068    import org.deegree.model.spatialschema.GeometryFactory;
069    import org.deegree.ogcwebservices.LonLatEnvelope;
070    import org.deegree.ogcwebservices.wcs.configuration.File;
071    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
072    
073    import com.ermapper.ecw.JNCSException;
074    
075    /**
076     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
077     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
078     * 
079     * @version $Revision: 6259 $
080     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
081     * @author last edited by: $Author: bezema $
082     * 
083     * @version 1.0. $Revision: 6259 $, $Date: 2007-03-20 10:15:15 +0100 (Di, 20 Mär 2007) $
084     * 
085     * @since 2.0
086     */
087    
088    public class ImageGridCoverageReader extends AbstractGridCoverageReader {
089    
090        private static final ILogger LOGGER = LoggerFactory.getLogger( ImageGridCoverageReader.class );
091    
092        /**
093         * @param source
094         *            source file of the coverage
095         * @param description
096         *            description of the data contained in the source file
097         * @param envelope
098         *            desired envelope of the coverage to be read
099         * @param format
100         *            image format of the source file
101         */
102        public ImageGridCoverageReader( File source, CoverageOffering description, Envelope envelope,
103                                        Format format ) {
104            super( source, description, envelope, format );
105        }
106    
107        /**
108         * @param source
109         * @param description
110         *            description of the data contained in the source file
111         * @param envelope
112         *            desired envelope of the coverage to be read
113         * @param format
114         *            image format of the source file
115         */
116        public ImageGridCoverageReader( InputStream source, CoverageOffering description,
117                                        Envelope envelope, Format format ) {
118            super( source, description, envelope, format );
119        }
120    
121        /**
122         * Read the grid coverage from the current stream position, and move to the next grid coverage.
123         * 
124         * @param parameters
125         *            An optional set of parameters. Should be any or all of the parameters returned by
126         *            {@link "org.opengis.coverage.grid.Format#getReadParameters"}.
127         * @return A new {@linkplain GridCoverage grid coverage} from the input source.
128         * @throws InvalidParameterNameException
129         *             if a parameter in <code>parameters</code> doesn't have a recognized name.
130         * @throws InvalidParameterValueException
131         *             if a parameter in <code>parameters</code> doesn't have a valid value.
132         * @throws ParameterNotFoundException
133         *             if a parameter was required for the operation but was not provided in the
134         *             <code>parameters</code> list.
135         * @throws IOException
136         *             if a read operation failed for some other input/output reason, including
137         *             {@link java.io.FileNotFoundException} if no file with the given <code>name</code>
138         *             can be found, or {@link javax.imageio.IIOException} if an error was thrown by the
139         *             underlying image library.
140         */
141        public GridCoverage read( GeneralParameterValueIm[] parameters )
142                                throws InvalidParameterNameException, InvalidParameterValueException,
143                                ParameterNotFoundException, IOException {
144    
145            String frmt = description.getSupportedFormats().getNativeFormat().getCode();
146            GridCoverage gc = null;
147            if ( frmt.equalsIgnoreCase( "ecw" ) ) {
148                gc = performECW( parameters );
149            } else if ( frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" )
150                        || frmt.equalsIgnoreCase( "tif" ) || frmt.equalsIgnoreCase( "tiff" )
151                        || frmt.equalsIgnoreCase( "gif" ) || frmt.equalsIgnoreCase( "jpg" )
152                        || frmt.equalsIgnoreCase( "jpeg" ) ) {
153                gc = performImage();
154            } else {
155                throw new InvalidParameterValueException( "unknown format", "native format", format );
156            }
157    
158            return gc;
159        }
160    
161        /**
162         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
163         * reader.
164         * 
165         * @param parameters
166         * @return
167         * @throws IOException
168         */
169        private GridCoverage performECW( GeneralParameterValueIm[] parameters )
170                                throws IOException {
171    
172            BufferedImage bi = null;
173            CoverageOffering co = null;
174            Object[] o = null;
175    
176            ECWReader ecwFile = null;
177            try {
178                String s = ( (File) source ).getName();
179                ecwFile = new ECWReader( s );
180    
181                // get the requested dimension in pixels
182                int reqWidth = 0;
183                int reqHeight = 0;
184                for ( int i = 0; i < parameters.length; i++ ) {
185                    OperationParameterIm op = (OperationParameterIm) parameters[i].getDescriptor();
186                    String name = op.getName();
187                    if ( name.equalsIgnoreCase( "WIDTH" ) ) {
188                        Object vo = op.getDefaultValue();
189                        reqWidth = ( (Integer) vo ).intValue();
190                    } else if ( name.equalsIgnoreCase( "HEIGHT" ) ) {
191                        Object vo = op.getDefaultValue();
192                        reqHeight = ( (Integer) vo ).intValue();
193                    }
194                }
195    
196                // calculate image region of interest
197                o = getECWImageRegion( reqWidth, reqHeight );
198                Envelope envl = (Envelope) o[1];
199    
200                Rectangle rect = (Rectangle) o[0];
201                bi = ecwFile.getBufferedImage( envl, rect.width, rect.height );
202    
203                // create a coverage description that matches the sub image (coverage)
204                // for this a new LonLatEnvelope must be set
205                co = (CoverageOffering) description.clone();
206                co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
207    
208            } catch ( JNCSException e ) {
209                throw new IOException( StringTools.stackTraceToString( e ) );
210            } finally {
211                // free the ECW cache memory
212                if ( ecwFile != null ) {
213                    ecwFile.close();
214                }
215            }
216    
217            return new ImageGridCoverage( co, (Envelope) o[1], bi );
218    
219        }
220    
221        /**
222         * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
223         * reader.
224         * 
225         * @return
226         * @throws IOException
227         * @throws ParameterNotFoundException
228         */
229        private GridCoverage performImage()
230                                throws ParameterNotFoundException, IOException {
231    
232            BufferedImage bi = readImage();
233    
234            // get image rectangle of interrest, envelope and lonlatenvelope
235            Object[] o = getImageRegion( bi.getWidth(), bi.getHeight() );
236            Rectangle rect = (Rectangle) o[0];
237            // return null if the result GC would have a width or height of zero
238            if ( rect.width == 0 || rect.height == 0 ) {
239                return null;
240            }
241            bi = bi.getSubimage( rect.x, rect.y, rect.width, rect.height );
242    
243            // create a coverage description that matches the sub image (coverage)
244            // for this a new LonLatEnvelope must be set
245            CoverageOffering co = (CoverageOffering) description.clone();
246            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
247    
248            return new ImageGridCoverage( co, (Envelope) o[1], bi );
249        }
250    
251        /**
252         * reads an image from its source
253         * 
254         * @return
255         * @throws IOException
256         */
257        private BufferedImage readImage()
258                                throws IOException {
259            BufferedImage bi = null;
260            if ( source.getClass() == File.class ) {
261                String s = ( (File) source ).getName();
262                String tmp = s.toLowerCase();
263                if ( tmp.startsWith( "file:" ) ) {
264                    tmp = s.substring( 6, s.length() );
265                    bi = ImageUtils.loadImage( new java.io.File( tmp ) );
266                } else if ( tmp.startsWith( "http:" ) ) {
267                    bi = ImageUtils.loadImage( new URL( s ) );
268                } else {
269                    bi = ImageUtils.loadImage( new java.io.File( s ) );
270                }
271            } else {
272                bi = ImageUtils.loadImage( (InputStream) source );
273            }
274            return bi;
275        }
276    
277        /**
278         * Return the SRS code of our native SRS.
279         */
280        private String getNativeSRSCode() {
281            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
282            return cl[0].getCodes()[0];
283        }
284    
285        /**
286         * return the LonLatEnvelope of the entire image in "EPSG:4326"
287         */
288        private Envelope getLLEAsEnvelope() {
289            String code = getNativeSRSCode();
290            LonLatEnvelope lle = description.getLonLatEnvelope();
291            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(),
292                                                           lle.getMax().getX(), lle.getMax().getY(),
293                                                           null );
294            try {
295                if ( !code.equals( "EPSG:4326" ) ) {
296                    IGeoTransformer trans = new GeoTransformer( code );
297                    tmp = trans.transform( tmp, "EPSG:4326" );
298                }
299            } catch ( Exception e ) {
300                LOGGER.logError( StringTools.stackTraceToString( e ) );
301            }
302    
303            return tmp;
304        }
305    
306        /**
307         * Calculate the rectangle that belongs to the given destination envelope in the given source
308         * image.
309         */
310        private Object[] calcRegionRectangle( Envelope dstenv, Envelope srcenv, double srcwidth,
311                                              double srcheight ) {
312            GeoTransform gt = new WorldToScreenTransform( srcenv.getMin().getX(),
313                                                          srcenv.getMin().getY(),
314                                                          srcenv.getMax().getX(),
315                                                          srcenv.getMax().getY(), 0, 0, srcwidth - 1,
316                                                          srcheight - 1 );
317    
318            int minx = (int) Math.round( gt.getDestX( dstenv.getMin().getX() ) );
319            int miny = (int) Math.round( gt.getDestY( dstenv.getMax().getY() ) );
320            int maxx = (int) Math.round( gt.getDestX( dstenv.getMax().getX() ) );
321            int maxy = (int) Math.round( gt.getDestY( dstenv.getMin().getY() ) );
322            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
323            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( dstenv, getNativeSRSCode() );
324    
325            return new Object[] { rect, dstenv, lonLatEnvelope };
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.
332         * 
333         * @param imgwidth
334         *            width of the source image
335         * @param imgheight
336         *            height of the source image
337         * @return the region of the source image that intersects with the GridCoverage to be created as
338         *         Rectangle as well as the Envelope of the region in the native CRS and the
339         *         LonLatEnvelope of this region.
340         */
341        private Object[] getImageRegion( int imgwidth, int imgheight ) {
342    
343            Envelope imgenvelope = getLLEAsEnvelope();
344            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
345            return calcRegionRectangle( dstenvelope, imgenvelope, imgwidth, imgheight );
346        }
347    
348        /**
349         * returns the region of the source image that intersects with the GridCoverage to be created as
350         * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
351         * this region. For ECW files these values reflect exactly the desired result, as the library
352         * will cut and sample the source image during read.
353         * 
354         * @param reqwidth
355         *            width of the requested region
356         * @param reqheight
357         *            height of the requested region
358         * @return the region of the source image that intersects with the GridCoverage to be created as
359         *         Rectangle as well as the Envelope of the region in the native CRS and the
360         *         LonLatEnvelope of this region.
361         */
362        private Object[] getECWImageRegion( int reqwidth, int reqheight ) {
363    
364            Envelope imgenvelope = getLLEAsEnvelope();
365            Envelope dstenvelope = envelope.createIntersection( imgenvelope );
366    
367            // In case of ECW files, we calculate the rectangle according
368            // to the desired result, not according to the source image,
369            // as clipping and sampling will be done by the ECW library.
370            // Hence dstenvelope and srcenvelope are the same and width/height
371            // must be clipped with our grid-cell (if necessary).
372            double dWidth = ( dstenvelope.getWidth() * reqwidth ) / envelope.getWidth();
373            double dHeight = ( dstenvelope.getHeight() * reqheight ) / envelope.getHeight();
374            return calcRegionRectangle( dstenvelope, dstenvelope, dWidth, dHeight );
375        }
376    
377        /**
378         * Allows any resources held by this object to be released. The result of calling any other
379         * method subsequent to a call to this method is undefined. It is important for applications to
380         * call this method when they know they will no longer be using this
381         * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to
382         * resources indefinitely.
383         * 
384         * @throws IOException
385         *             if an error occured while disposing resources (for example while closing a file).
386         */
387        public void dispose()
388                                throws IOException {
389            if ( source instanceof InputStream ) {
390                ( (InputStream) source ).close();
391            }
392        }
393    
394    }
395    /***************************************************************************************************
396     * <code>
397     Changes to this class. What the people have been up to:
398     
399     $Log$
400     Revision 1.23  2007/03/13 12:45:25  wanhoff
401     Fixed Javadoc (@link, @return)
402    
403     Revision 1.22  2007/02/09 17:28:00  poth
404     *** empty log message ***
405    
406     Revision 1.21  2006/11/27 09:07:52  poth
407     JNI integration of proj4 has been removed. The CRS functionality now will be done by native deegree code.
408    
409     Revision 1.20  2006/10/27 06:29:37  poth
410     bug fix - missing else added in readImage method
411    
412     Revision 1.19  2006/10/17 15:00:18  poth
413     reading image files enhanced
414    
415     Revision 1.18  2006/09/27 16:46:41  poth
416     transformation method signature changed
417    
418     Revision 1.17  2006/09/22 13:44:45  poth
419     bug fix reading ECW
420    
421     Revision 1.16  2006/09/18 17:56:08  poth
422     bug fix - reading ECW-files
423    
424     Revision 1.15  2006/09/15 09:07:48  poth
425     ecw file to free memory
426    
427     Revision 1.14  2006/09/14 20:07:12  poth
428     bug fix reading ECW
429    
430     Revision 1.13  2006/08/08 09:10:11  poth
431     never read parameters removed
432    
433     Revision 1.12  2006/07/29 08:51:55  poth
434     bug fix - reading images from files/URLs
435    
436     Revision 1.11  2006/07/25 18:33:24  poth
437     bug fix - reading single file grid coverage
438    
439     Revision 1.10  2006/05/03 20:09:52  poth
440     *** empty log message ***
441    
442     Revision 1.9  2006/05/01 20:15:27  poth
443     *** empty log message ***
444    
445     Revision 1.8  2006/04/06 20:25:26  poth
446     *** empty log message ***
447    
448     Revision 1.7  2006/04/04 20:39:44  poth
449     *** empty log message ***
450    
451     Revision 1.6  2006/03/30 21:20:26  poth
452     *** empty log message ***
453    
454     Revision 1.5  2006/02/23 07:45:24  poth
455     *** empty log message ***
456    
457     Revision 1.4  2005/11/21 14:58:25  deshmukh
458     CRS to SRS
459    
460     Revision 1.3  2005/09/27 19:53:18  poth
461     no message
462    
463     Revision 1.2  2005/01/18 22:08:54  poth
464     no message
465    
466     Revision 1.9  2004/08/23 06:59:52  ap
467     no message
468    
469     Revision 1.8  2004/08/09 06:43:50  ap
470     no message
471    
472     Revision 1.6  2004/08/06 06:41:51  ap
473     grid coverage implementation extension
474    
475    
476     </code>
477     **************************************************************************************************/