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 }