001    // $HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/branches/2.2_testing/src/org/deegree/model/coverage/grid/GeoTIFFGridCoverageReader.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.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.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.sun.media.jai.codec.MemoryCacheSeekableStream;
074    import com.sun.media.jai.codec.SeekableStream;
075    
076    /**
077     * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
078     * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
079     * 
080     * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
081     * @author last edited by: $Author: rbezema $
082     * 
083     * @version $Revision: 9437 $, $Date: 2008-01-08 11:50:52 +0100 (Di, 08 Jan 2008) $
084     */
085    public class GeoTIFFGridCoverageReader extends AbstractGridCoverageReader {
086    
087        private static final ILogger LOG = LoggerFactory.getLogger( GeoTIFFGridCoverageReader.class );
088    
089        private SeekableStream sst = null;
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 GeoTIFFGridCoverageReader( 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 GeoTIFFGridCoverageReader( 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            RenderedOp rop = readGeoTIFF();
143            int w = rop.getWidth();
144            int h = rop.getHeight();
145    
146            // get image rectangle of interrest, envelope and lonlatenvelope
147            Object[] o = getRasterRegion( w, h );
148            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
149                LOG.logDebug( "image rectangle of interrest, envelope and lonlatenvelope:" );
150                for ( int i = 0; i < o.length; i++ ) {
151                    LOG.logDebug( o[i].toString() );
152                }
153            }
154            Rectangle rect = (Rectangle) o[0];
155            // return null if the result GC would have a width or height of zero
156            if ( rect.width == 0 || rect.height == 0 ) {
157                return null;
158            }
159            // create a coverage description that matches the sub image (coverage)
160            // for this a new LonLatEnvelope must be set
161            CoverageOffering co = (CoverageOffering) description.clone();
162            co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
163    
164            // extract required area from the tiff data
165            Raster raster = rop.getData( rect );
166            // use 8 BIT as default assuming a raster contains simple
167            // data like a Landsat TM Band
168            int pxSize = 8;
169            if ( rop.getColorModel() != null ) {
170                pxSize = rop.getColorModel().getPixelSize();
171            }
172    
173            return createGridCoverage( raster, co, (Envelope) o[1], pxSize );
174    
175        }
176    
177        /**
178         * creates an instance of <tt>GridCoverage</tt> from the passed Raster, CoverageOffering and
179         * Envelope. Depending on the pixel size of the the passed raster different types of
180         * GirdCoverages will be created. possilbe pixel sized are:
181         * <ul>
182         * <li>8
183         * <li>16
184         * <li>32
185         * <li>64
186         * </ul>
187         * 
188         * @param raster
189         * @param co
190         * @param env
191         * @param pxSize
192         * @return
193         * @throws InvalidParameterValueException
194         */
195        private GridCoverage createGridCoverage( Raster raster, CoverageOffering co, Envelope env, int pxSize )
196                                throws InvalidParameterValueException {
197    
198            GridCoverage gc = null;
199            switch ( pxSize ) {
200            case 8: {
201                gc = createByteGridCoverage( raster, co, env );
202                break;
203            }
204            case 16: {
205                gc = createShortGridCoverage( raster, co, env );
206                break;
207            }
208            case 32:
209            case 64: {
210                String s = Messages.getMessage( "GC_NOT_SUPPORTED_PS", pxSize );
211                throw new InvalidParameterValueException( s, "type", pxSize );
212            }
213            default:
214                String s = Messages.getMessage( "GC_UNKNOWN_PS", pxSize );
215                throw new InvalidParameterValueException( s, "type", pxSize );
216            }
217    
218            return gc;
219        }
220    
221        /**
222         * creates a GridCoverage from the passed Raster. The contains data in
223         * <tt>DataBuffer.TYPE_BYTE</tt> format so the result GridCoverage is of type
224         * <tt>ByteGridCoverage </tt>
225         * 
226         * @param raster
227         * @param co
228         * @param env
229         * @return
230         */
231        private ByteGridCoverage createByteGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
232    
233            Rectangle rect = raster.getBounds();
234            int bands = raster.getNumBands();
235            byte[] data = (byte[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
236            byte[][][] mat = new byte[bands][rect.height][rect.width];
237            int k = 0;
238            for ( int i = 0; i < mat[0].length; i++ ) {
239                for ( int j = 0; j < mat[0][i].length; j++ ) {
240                    for ( int b = 0; b < bands; b++ ) {
241                        mat[b][i][j] = data[k++];
242                    }
243                }
244            }
245    
246            return new ByteGridCoverage( co, env, mat );
247        }
248    
249        /**
250         * creates a GridCoverage from the passed Raster. The contains data in
251         * <tt>DataBuffer.TYPE_SHORT</tt> format so the result GridCoverage is of type
252         * <tt>ShortGridCoverage </tt>
253         * 
254         * @param raster
255         * @param co
256         * @param env
257         * @return
258         */
259        private ShortGridCoverage createShortGridCoverage( Raster raster, CoverageOffering co, Envelope env ) {
260    
261            Rectangle rect = raster.getBounds();
262            int bands = raster.getNumBands();
263            short[] data = (short[]) raster.getDataElements( rect.x, rect.y, rect.width, rect.height, null );
264            short[][][] mat = new short[bands][rect.height][rect.width];
265            int k = 0;
266            for ( int i = 0; i < mat[0].length; i++ ) {
267                for ( int j = 0; j < mat[0][i].length; j++ ) {
268                    for ( int b = 0; b < bands; b++ ) {
269                        mat[b][i][j] = data[k++];
270                    }
271                }
272            }
273    
274            return new ShortGridCoverage( co, env, mat );
275        }
276    
277        /**
278         * reads an image from its source
279         * 
280         * @return
281         * @throws IOException
282         */
283        private RenderedOp readGeoTIFF()
284                                throws IOException {
285    
286            RenderedOp ro = null;
287            if ( source.getClass() == File.class ) {
288                String s = ( (File) source ).getName();
289                String tmp = s.toLowerCase();
290                LOG.logDebug( "load: ", tmp );
291                URL url = null;
292                if ( tmp.startsWith( "file:" ) ) {
293                    tmp = s.substring( 6, s.length() );
294                    url = new java.io.File( tmp ).toURL();
295                } else if ( tmp.startsWith( "http:" ) ) {
296                    url = new URL( s );
297                } else {
298                    url = new java.io.File( s ).toURL();
299                }
300                sst = new MemoryCacheSeekableStream( url.openStream() );
301                ro = JAI.create( "stream", sst );
302            } else {
303                sst = new MemoryCacheSeekableStream( (InputStream) source );
304                ro = JAI.create( "stream", sst );
305            }
306    
307            return ro;
308        }
309    
310        /**
311         * returns the region of the source image that intersects with the GridCoverage to be created as
312         * Rectange as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
313         * this region.
314         * 
315         * @param width
316         *            width of the source image
317         * @param height
318         *            height of the source image
319         * @return
320         */
321        private Object[] getRasterRegion( int width, int height ) {
322    
323            CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
324            String code = cl[0].getCodes()[0];
325    
326            LonLatEnvelope lle = description.getLonLatEnvelope();
327            Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
328                                                           lle.getMax().getY(), null );
329            try {
330                // transform if native CRS is <> EPSG:4326
331                if ( !( code.equals( "EPSG:4326" ) ) ) {
332                    GeoTransformer trans = new GeoTransformer( code );
333                    tmp = trans.transform( tmp, "EPSG:4326" );
334                }
335            } catch ( Exception e ) {
336                LOG.logError( StringTools.stackTraceToString( e ) );
337            }
338            // creat tranform object to calculate raster coordinates from
339            // geo coordinates
340            GeoTransform gt = new WorldToScreenTransform( tmp.getMin().getX(), tmp.getMin().getY(), tmp.getMax().getX(),
341                                                          tmp.getMax().getY(), 0, 0, width - 1, height - 1 );
342    
343            // calculate envelope of the part of the grid coverage that is contained
344            // within the image
345            Envelope env = envelope.createIntersection( tmp );
346    
347            LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( env, code );
348            // calc image coordinates matching the area that is requested
349            int minx = (int) Math.round( gt.getDestX( env.getMin().getX() ) );
350            int miny = (int) Math.round( gt.getDestY( env.getMax().getY() ) );
351            int maxx = (int) Math.round( gt.getDestX( env.getMax().getX() ) );
352            int maxy = (int) Math.round( gt.getDestY( env.getMin().getY() ) );
353            Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
354    
355            return new Object[] { rect, env, lonLatEnvelope };
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 ( sst != null ) {
371                sst.close();
372            }
373        }
374    
375    }