001 // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_testing/src/org/deegree/model/coverage/grid/ImageGridCoverageReader.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.BufferedImage;
040 import java.io.IOException;
041 import java.io.InputStream;
042 import java.net.URL;
043
044 import org.deegree.datatypes.CodeList;
045 import org.deegree.datatypes.parameter.GeneralParameterValueIm;
046 import org.deegree.datatypes.parameter.InvalidParameterNameException;
047 import org.deegree.datatypes.parameter.InvalidParameterValueException;
048 import org.deegree.datatypes.parameter.OperationParameterIm;
049 import org.deegree.datatypes.parameter.ParameterNotFoundException;
050 import org.deegree.framework.log.ILogger;
051 import org.deegree.framework.log.LoggerFactory;
052 import org.deegree.framework.util.ImageUtils;
053 import org.deegree.framework.util.StringTools;
054 import org.deegree.graphics.transformation.GeoTransform;
055 import org.deegree.graphics.transformation.WorldToScreenTransform;
056 import org.deegree.io.ecwapi.ECWReader;
057 import org.deegree.model.crs.GeoTransformer;
058 import org.deegree.model.spatialschema.Envelope;
059 import org.deegree.model.spatialschema.GeometryFactory;
060 import org.deegree.ogcwebservices.LonLatEnvelope;
061 import org.deegree.ogcwebservices.wcs.configuration.File;
062 import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering;
063
064 import com.ermapper.ecw.JNCSException;
065
066 /**
067 * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type
068 * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI)
069 *
070 * @version $Revision: 18195 $
071 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a>
072 * @author last edited by: $Author: mschneider $
073 *
074 * @version 1.0. $Revision: 18195 $, $Date: 2009-06-18 17:55:39 +0200 (Do, 18. Jun 2009) $
075 *
076 * @since 2.0
077 */
078
079 public class ImageGridCoverageReader extends AbstractGridCoverageReader {
080
081 private static final ILogger LOGGER = LoggerFactory.getLogger( ImageGridCoverageReader.class );
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 ImageGridCoverageReader( 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 ImageGridCoverageReader( 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 String frmt = description.getSupportedFormats().getNativeFormat().getCode();
135 GridCoverage gc = null;
136 if ( frmt.equalsIgnoreCase( "ecw" ) ) {
137 gc = performECW( parameters );
138 } else if ( frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" ) || frmt.equalsIgnoreCase( "tif" )
139 || frmt.equalsIgnoreCase( "tiff" ) || frmt.equalsIgnoreCase( "gif" )
140 || frmt.equalsIgnoreCase( "jpg" ) || frmt.equalsIgnoreCase( "jpeg" ) ) {
141 gc = performImage();
142 } else {
143 throw new InvalidParameterValueException( "unknown format", "native format", format );
144 }
145
146 return gc;
147 }
148
149 /**
150 * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
151 * reader.
152 *
153 * @param parameters
154 * @throws IOException
155 */
156 private GridCoverage performECW( GeneralParameterValueIm[] parameters )
157 throws IOException {
158
159 BufferedImage bi = null;
160 CoverageOffering co = null;
161 Object[] o = null;
162
163 ECWReader ecwFile = null;
164 try {
165 String s = ( (File) source ).getName();
166 ecwFile = new ECWReader( s );
167
168 // get the requested dimension in pixels
169 int reqWidth = 0;
170 int reqHeight = 0;
171 for ( int i = 0; i < parameters.length; i++ ) {
172 OperationParameterIm op = (OperationParameterIm) parameters[i].getDescriptor();
173 String name = op.getName();
174 if ( name.equalsIgnoreCase( "WIDTH" ) ) {
175 Object vo = op.getDefaultValue();
176 reqWidth = ( (Integer) vo ).intValue();
177 } else if ( name.equalsIgnoreCase( "HEIGHT" ) ) {
178 Object vo = op.getDefaultValue();
179 reqHeight = ( (Integer) vo ).intValue();
180 }
181 }
182
183 // calculate image region of interest
184 o = getECWImageRegion( reqWidth, reqHeight );
185 Envelope envl = (Envelope) o[1];
186
187 Rectangle rect = (Rectangle) o[0];
188 bi = ecwFile.getBufferedImage( envl, rect.width, rect.height );
189
190 // create a coverage description that matches the sub image (coverage)
191 // for this a new LonLatEnvelope must be set
192 co = (CoverageOffering) description.clone();
193 co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
194
195 } catch ( JNCSException e ) {
196 throw new IOException( StringTools.stackTraceToString( e ) );
197 } finally {
198 // free the ECW cache memory
199 if ( ecwFile != null ) {
200 ecwFile.close();
201 }
202 }
203
204 return new ImageGridCoverage( co, (Envelope) o[1], bi );
205
206 }
207
208 /**
209 * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this
210 * reader.
211 *
212 * @throws IOException
213 * @throws ParameterNotFoundException
214 */
215 private GridCoverage performImage()
216 throws ParameterNotFoundException, IOException {
217
218 BufferedImage bi = readImage();
219
220 // get image rectangle of interrest, envelope and lonlatenvelope
221 Object[] o = getImageRegion( bi.getWidth(), bi.getHeight() );
222 Rectangle rect = (Rectangle) o[0];
223 // return null if the result GC would have a width or height of zero
224 if ( rect.width == 0 || rect.height == 0 ) {
225 return null;
226 }
227 bi = bi.getSubimage( rect.x, rect.y, rect.width, rect.height );
228
229 // create a coverage description that matches the sub image (coverage)
230 // for this a new LonLatEnvelope must be set
231 CoverageOffering co = (CoverageOffering) description.clone();
232 co.setLonLatEnvelope( (LonLatEnvelope) o[2] );
233
234 return new ImageGridCoverage( co, (Envelope) o[1], bi );
235 }
236
237 /**
238 * reads an image from its source
239 *
240 * @throws IOException
241 */
242 private BufferedImage readImage()
243 throws IOException {
244 BufferedImage bi = null;
245 if ( source.getClass() == File.class ) {
246 String s = ( (File) source ).getName();
247 String tmp = s.toLowerCase();
248 if ( tmp.startsWith( "file:" ) ) {
249 tmp = s.substring( 6, s.length() );
250 bi = ImageUtils.loadImage( new java.io.File( tmp ) );
251 } else if ( tmp.startsWith( "http:" ) ) {
252 bi = ImageUtils.loadImage( new URL( s ) );
253 } else {
254 bi = ImageUtils.loadImage( new java.io.File( s ) );
255 }
256 } else {
257 bi = ImageUtils.loadImage( (InputStream) source );
258 }
259 return bi;
260 }
261
262 /**
263 * Return the SRS code of our native SRS.
264 */
265 private String getNativeSRSCode() {
266 CodeList[] cl = description.getSupportedCRSs().getNativeSRSs();
267 return cl[0].getCodes()[0];
268 }
269
270 /**
271 * return the LonLatEnvelope of the entire image in "EPSG:4326"
272 */
273 private Envelope getLLEAsEnvelope() {
274 String code = getNativeSRSCode();
275 LonLatEnvelope lle = description.getLonLatEnvelope();
276 Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(),
277 lle.getMax().getY(), null );
278 try {
279 if ( !code.equals( "EPSG:4326" ) ) {
280 GeoTransformer trans = new GeoTransformer( code );
281 tmp = trans.transform( tmp, "EPSG:4326" );
282 }
283 } catch ( Exception e ) {
284 LOGGER.logError( StringTools.stackTraceToString( e ) );
285 }
286
287 return tmp;
288 }
289
290 /**
291 * Calculate the rectangle that belongs to the given destination envelope in the given source
292 * image.
293 */
294 private Object[] calcRegionRectangle( Envelope dstenv, Envelope srcenv, double srcwidth, double srcheight ) {
295 GeoTransform gt = new WorldToScreenTransform( srcenv.getMin().getX(), srcenv.getMin().getY(),
296 srcenv.getMax().getX(), srcenv.getMax().getY(), 0, 0,
297 srcwidth - 1, srcheight - 1 );
298
299 int minx = (int) Math.round( gt.getDestX( dstenv.getMin().getX() ) );
300 int miny = (int) Math.round( gt.getDestY( dstenv.getMax().getY() ) );
301 int maxx = (int) Math.round( gt.getDestX( dstenv.getMax().getX() ) );
302 int maxy = (int) Math.round( gt.getDestY( dstenv.getMin().getY() ) );
303 Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 );
304 LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( dstenv, getNativeSRSCode() );
305
306 return new Object[] { rect, dstenv, lonLatEnvelope };
307 }
308
309 /**
310 * returns the region of the source image that intersects with the GridCoverage to be created as
311 * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
312 * this region.
313 *
314 * @param imgwidth
315 * width of the source image
316 * @param imgheight
317 * height of the source image
318 * @return the region of the source image that intersects with the GridCoverage to be created as
319 * Rectangle as well as the Envelope of the region in the native CRS and the
320 * LonLatEnvelope of this region.
321 */
322 private Object[] getImageRegion( int imgwidth, int imgheight ) {
323
324 Envelope imgenvelope = getLLEAsEnvelope();
325 Envelope dstenvelope = envelope.createIntersection( imgenvelope );
326 return calcRegionRectangle( dstenvelope, imgenvelope, imgwidth, imgheight );
327 }
328
329 /**
330 * returns the region of the source image that intersects with the GridCoverage to be created as
331 * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of
332 * this region. For ECW files these values reflect exactly the desired result, as the library
333 * will cut and sample the source image during read.
334 *
335 * @param reqwidth
336 * width of the requested region
337 * @param reqheight
338 * height of the requested region
339 * @return the region of the source image that intersects with the GridCoverage to be created as
340 * Rectangle as well as the Envelope of the region in the native CRS and the
341 * LonLatEnvelope of this region.
342 */
343 private Object[] getECWImageRegion( int reqwidth, int reqheight ) {
344
345 Envelope imgenvelope = getLLEAsEnvelope();
346 Envelope dstenvelope = envelope.createIntersection( imgenvelope );
347
348 // In case of ECW files, we calculate the rectangle according
349 // to the desired result, not according to the source image,
350 // as clipping and sampling will be done by the ECW library.
351 // Hence dstenvelope and srcenvelope are the same and width/height
352 // must be clipped with our grid-cell (if necessary).
353 double dWidth = ( dstenvelope.getWidth() * reqwidth ) / envelope.getWidth();
354 double dHeight = ( dstenvelope.getHeight() * reqheight ) / envelope.getHeight();
355 return calcRegionRectangle( dstenvelope, dstenvelope, dWidth, dHeight );
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 ( source instanceof InputStream ) {
371 ( (InputStream) source ).close();
372 }
373 }
374
375 }