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