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