001    // $HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/model/coverage/grid/GeoTIFFGridCoverageReader.java $
002    /*----------------    FILE HEADER  ------------------------------------------
003    
004     This file is part of deegree.
005     Copyright (C) 2001-2007 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.Raster;
048    import java.io.IOException;
049    import java.io.InputStream;
050    import java.net.URL;
051    
052    import javax.media.jai.JAI;
053    import javax.media.jai.RenderedOp;
054    
055    import org.deegree.datatypes.CodeList;
056    import org.deegree.datatypes.parameter.GeneralParameterValueIm;
057    import org.deegree.datatypes.parameter.InvalidParameterNameException;
058    import org.deegree.datatypes.parameter.InvalidParameterValueException;
059    import org.deegree.datatypes.parameter.ParameterNotFoundException;
060    import org.deegree.framework.log.ILogger;
061    import org.deegree.framework.log.LoggerFactory;
062    import org.deegree.framework.util.StringTools;
063    import org.deegree.graphics.transformation.GeoTransform;
064    import org.deegree.graphics.transformation.WorldToScreenTransform;
065    import org.deegree.i18n.Messages;
066    import org.deegree.model.crs.GeoTransformer;
067    import org.deegree.model.crs.IGeoTransformer;
068    import org.deegree.model.spatialschema.Envelope;
069    import org.deegree.model.spatialschema.GeometryFactory;
070    import org.deegree.ogcwebservices.LonLatEnvelope;
071    import org.deegree.ogcwebservices.wcs.configuration.File;
072    import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
073    
074    import com.sun.media.jai.codec.MemoryCacheSeekableStream;
075    import com.sun.media.jai.codec.SeekableStream;
076    
077    /**
078     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
079     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
080     * 
081     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
082     * @author last edited by: $Author: apoth $
083     * 
084     * @version $Revision: 7587 $, $Date: 2007-06-19 11:29:12 +0200 (Di, 19 Jun 2007) $
085     */
086    public class GeoTIFFGridCoverageReader extends AbstractGridCoverageReader {
087    
088        private static final ILogger LOG = LoggerFactory.getLogger( GeoTIFFGridCoverageReader.class );
089    
090        private SeekableStream sst = null;
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 GeoTIFFGridCoverageReader( File source, CoverageOffering description, Envelope envelope, Format format ) {
103            super( source, description, envelope, format );
104        }
105    
106        /**
107         * @param source
108         * @param description
109         *            description of the data contained in the source file
110         * @param envelope
111         *            desired envelope of the coverage to be read
112         * @param format
113         *            image format of the source file
114         */
115        public GeoTIFFGridCoverageReader( InputStream source, CoverageOffering description, Envelope envelope, Format format ) {
116            super( source, description, envelope, format );
117        }
118    
119        /**
120         * Read the grid coverage from the current stream position, and move to the next grid coverage.
121         * 
122         * @param parameters
123         *            An optional set of parameters. Should be any or all of the parameters returned by
124         *            {@link "org.opengis.coverage.grid.Format#getReadParameters"}.
125         * @return A new {@linkplain GridCoverage grid coverage} from the input source.
126         * @throws InvalidParameterNameException
127         *             if a parameter in <code>parameters</code> doesn't have a recognized name.
128         * @throws InvalidParameterValueException
129         *             if a parameter in <code>parameters</code> doesn't have a valid value.
130         * @throws ParameterNotFoundException
131         *             if a parameter was required for the operation but was not provided in the
132         *             <code>parameters</code> list.
133         * @throws IOException
134         *             if a read operation failed for some other input/output reason, including
135         *             {@link java.io.FileNotFoundException} if no file with the given <code>name</code>
136         *             can be found, or {@link javax.imageio.IIOException} if an error was thrown by the
137         *             underlying image library.
138         */
139        public GridCoverage read( GeneralParameterValueIm[] parameters )
140                                throws InvalidParameterNameException, InvalidParameterValueException,
141                                ParameterNotFoundException, IOException {
142    
143            RenderedOp rop = readGeoTIFF();
144            int w = rop.getWidth();
145            int h = rop.getHeight();
146    
147            // get image rectangle of interrest, envelope and lonlatenvelope
148            Object[] o = getRasterRegion( w, h );
149            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
150                LOG.logDebug( "image rectangle of interrest, envelope and lonlatenvelope:" );
151                for ( int i = 0; i < o.length; i++ ) {                
152                    LOG.logDebug( o[i].toString() );
153                }
154            }
155            Rectangle rect = (Rectangle) o[0];
156            // return null if the result GC would have a width or height of zero
157            if ( rect.width == 0 || rect.height == 0 ) {
158                return null;
159            }
160            // create a coverage description that matches the sub image (coverage)
161            // for this a new LonLatEnvelope must be set
162            CoverageOffering co = (CoverageOffering) description.clone();
163            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
164    
165            // extract required area from the tiff data
166            Raster raster = rop.getData( rect );
167            // use 8 BIT as default assuming a raster contains simple
168            // data like a Landsat TM Band
169            int pxSize = 8;
170            if ( rop.getColorModel() != null ) {
171                pxSize = rop.getColorModel().getPixelSize();
172            }
173    
174            return createGridCoverage( raster, co, (Envelope) o[1], pxSize );
175    
176        }
177    
178        /**
179         * creates an instance of <tt>GridCoverage</tt> from the passed Raster, CoverageOffering and
180         * Envelope. Depending on the pixel size of the the passed raster different types of
181         * GirdCoverages will be created. possilbe pixel sized are:
182         * <ul>
183         * <li>8
184         * <li>16
185         * <li>32
186         * <li>64
187         * </ul>
188         * 
189         * @param raster
190         * @param co
191         * @param env
192         * @param pxSize
193         * @return
194         * @throws InvalidParameterValueException
195         */
196        private GridCoverage createGridCoverage( Raster raster, CoverageOffering co, Envelope env, int pxSize )
197                                throws InvalidParameterValueException {
198    
199            GridCoverage gc = null;
200            switch ( pxSize ) {
201            case 8: {
202                gc = createByteGridCoverage( raster, co, env );
203                break;
204            }
205            case 16: {
206                gc = createShortGridCoverage( raster, co, env );
207                break;
208            }
209            case 32:
210            case 64: {
211                String s = Messages.getMessage( "GC_NOT_SUPPORTED_PS", pxSize );
212                throw new InvalidParameterValueException( s, "type", pxSize );
213            }
214            default:
215                String s = Messages.getMessage( "GC_UNKNOWN_PS", pxSize );
216                throw new InvalidParameterValueException( s, "type", pxSize );
217            }
218    
219            return gc;
220        }
221    
222        /**
223         * creates a GridCoverage from the passed Raster. The contains data in
224         * <tt>DataBuffer.TYPE_BYTE</tt> format so the result GridCoverage is of type
225         * <tt>ByteGridCoverage </tt>
226         * 
227         * @param raster
228         * @param co
229         * @param env
230         * @return
231         */
232        private ByteGridCoverage createByteGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
233    
234            Rectangle rect = raster.getBounds();
235            int bands = raster.getNumBands();
236            byte[] data = (byte[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
237            byte[][][] mat = new byte[bands][rect.height][rect.width];
238            int k = 0;
239            for ( int i = 0; i < mat[0].length; i++ ) {
240                for ( int j = 0; j < mat[0][i].length; j++ ) {
241                    for ( int b = 0; b < bands; b++ ) {
242                        mat[b][i][j] = data[k++];
243                    }
244                }
245            }
246    
247            return new ByteGridCoverage( co, env, mat );
248        }
249    
250        /**
251         * creates a GridCoverage from the passed Raster. The contains data in
252         * <tt>DataBuffer.TYPE_SHORT</tt> format so the result GridCoverage is of type
253         * <tt>ShortGridCoverage </tt>
254         * 
255         * @param raster
256         * @param co
257         * @param env
258         * @return
259         */
260        private ShortGridCoverage createShortGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
261    
262            Rectangle rect = raster.getBounds();
263            int bands = raster.getNumBands();
264            short[] data = (short[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
265            short[][][] mat = new short[bands][rect.height][rect.width];
266            int k = 0;
267            for ( int i = 0; i < mat[0].length; i++ ) {
268                for ( int j = 0; j < mat[0][i].length; j++ ) {
269                    for ( int b = 0; b < bands; b++ ) {
270                        mat[b][i][j] = data[k++];
271                    }
272                }
273            }
274    
275            return new ShortGridCoverage( co, env, mat );
276        }
277    
278        /**
279         * reads an image from its source
280         * 
281         * @return
282         * @throws IOException
283         */
284        private RenderedOp readGeoTIFF()
285                                throws IOException {
286    
287            RenderedOp ro = null;
288            if ( source.getClass() == File.class ) {
289                String s = ( (File) source ).getName();
290                String tmp = s.toLowerCase();
291                LOG.logDebug( "load: ", tmp );
292                URL url = null;
293                if ( tmp.startsWith( "file:" ) ) {
294                    tmp = s.substring( 6, s.length() );
295                    url = new java.io.File( tmp ).toURL();
296                } else if ( tmp.startsWith( "http:" ) ) {
297                    url = new URL( s );
298                } else {
299                    url = new java.io.File( s ).toURL();
300                }
301                sst = new MemoryCacheSeekableStream( url.openStream() );
302                ro = JAI.create( "stream", sst );
303            } else {
304                sst = new MemoryCacheSeekableStream( (InputStream) source );
305                ro = JAI.create( "stream", sst );
306            }
307    
308            return ro;
309        }
310    
311        /**
312         * returns the region of the source image that intersects with the GridCoverage to be created as
313         * Rectange as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
314         * this region.
315         * 
316         * @param width
317         *            width of the source image
318         * @param height
319         *            height of the source image
320         * @return
321         */
322        private Object[] getRasterRegion( int width, int height ) {
323    
324            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
325            String code = cl[0].getCodes()[0];
326    
327            LonLatEnvelope lle = description.getLonLatEnvelope();
328            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
329                                                           lle.getMax().getY(), null );
330            try {
331                // transform if native CRS is <> EPSG:4326
332                if ( !( code.equals( "EPSG:4326" ) ) ) {
333                    IGeoTransformer trans = new GeoTransformer( code );
334                    tmp = trans.transform( tmp, "EPSG:4326" );
335                }
336            } catch ( Exception e ) {
337                LOG.logError( StringTools.stackTraceToString( e ) );
338            }
339            // creat tranform object to calculate raster coordinates from
340            // geo coordinates
341            GeoTransform gt = new WorldToScreenTransform( tmp.getMin().getX(), tmp.getMin().getY(), tmp.getMax().getX(),
342                                                          tmp.getMax().getY(), 0, 0, width - 1, height - 1 );
343    
344            // calculate envelope of the part of the grid coverage that is contained
345            // within the image        
346            Envelope env = envelope.createIntersection( tmp );
347    
348            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( env, code );
349            // calc image coordinates matching the area that is requested
350            int minx = (int) Math.round( gt.getDestX( env.getMin().getX() ) );
351            int miny = (int) Math.round( gt.getDestY( env.getMax().getY() ) );
352            int maxx = (int) Math.round( gt.getDestX( env.getMax().getX() ) );
353            int maxy = (int) Math.round( gt.getDestY( env.getMin().getY() ) );
354            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
355    
356            return new Object[] { rect, env, lonLatEnvelope };
357        }
358    
359        /**
360         * Allows any resources held by this object to be released. The result of calling any other
361         * method subsequent to a call to this method is undefined. It is important for applications to
362         * call this method when they know they will no longer be using this
363         * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to
364         * resources indefinitely.
365         * 
366         * @throws IOException
367         *             if an error occured while disposing resources (for example while closing a file).
368         */
369        public void dispose()
370                                throws IOException {
371            if ( sst != null ) {
372                sst.close();
373            }
374        }
375    
376    }