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 }