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 }