001 // $HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_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 /** 065 * GridCoverageReader for reading files as defined by the deegree CoverageOffering Extension type 066 * 'File'. Known formats are: tiff, GeoTiff, jpeg, bmp, gif, png and img (IDRISI) 067 * 068 * @version $Revision: 21400 $ 069 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 070 * @author last edited by: $Author: apoth $ 071 * 072 * @version 1.0. $Revision: 21400 $, $Date: 2009-12-11 12:01:34 +0100 (Fr, 11 Dez 2009) $ 073 * 074 * @since 2.0 075 */ 076 077 public class ImageGridCoverageReader extends AbstractGridCoverageReader { 078 079 private static final ILogger LOGGER = LoggerFactory.getLogger( ImageGridCoverageReader.class ); 080 081 /** 082 * @param source 083 * source file of the coverage 084 * @param description 085 * description of the data contained in the source file 086 * @param envelope 087 * desired envelope of the coverage to be read 088 * @param format 089 * image format of the source file 090 */ 091 public ImageGridCoverageReader( File source, CoverageOffering description, Envelope envelope, Format format ) { 092 super( source, description, envelope, format ); 093 } 094 095 /** 096 * @param source 097 * @param description 098 * description of the data contained in the source file 099 * @param envelope 100 * desired envelope of the coverage to be read 101 * @param format 102 * image format of the source file 103 */ 104 public ImageGridCoverageReader( InputStream source, CoverageOffering description, Envelope envelope, Format format ) { 105 super( source, description, envelope, format ); 106 } 107 108 /** 109 * Read the grid coverage from the current stream position, and move to the next grid coverage. 110 * 111 * @param parameters 112 * An optional set of parameters. Should be any or all of the parameters returned by 113 * {@link "org.opengis.coverage.grid.Format#getReadParameters"}. 114 * @return A new {@linkplain GridCoverage grid coverage} from the input source. 115 * @throws InvalidParameterNameException 116 * if a parameter in <code>parameters</code> doesn't have a recognized name. 117 * @throws InvalidParameterValueException 118 * if a parameter in <code>parameters</code> doesn't have a valid value. 119 * @throws ParameterNotFoundException 120 * if a parameter was required for the operation but was not provided in the 121 * <code>parameters</code> list. 122 * @throws IOException 123 * if a read operation failed for some other input/output reason, including 124 * {@link java.io.FileNotFoundException} if no file with the given <code>name</code> 125 * can be found, or {@link javax.imageio.IIOException} if an error was thrown by the 126 * underlying image library. 127 */ 128 public GridCoverage read( GeneralParameterValueIm[] parameters ) 129 throws InvalidParameterNameException, InvalidParameterValueException, 130 ParameterNotFoundException, IOException { 131 132 String frmt = description.getSupportedFormats().getNativeFormat().getCode(); 133 GridCoverage gc = null; 134 if ( frmt.equalsIgnoreCase( "ecw" ) ) { 135 gc = performECW( parameters ); 136 } else if ( frmt.equalsIgnoreCase( "png" ) || frmt.equalsIgnoreCase( "bmp" ) || frmt.equalsIgnoreCase( "tif" ) 137 || frmt.equalsIgnoreCase( "tiff" ) || frmt.equalsIgnoreCase( "gif" ) 138 || frmt.equalsIgnoreCase( "jpg" ) || frmt.equalsIgnoreCase( "jpeg" ) ) { 139 gc = performImage(); 140 } else { 141 throw new InvalidParameterValueException( "unknown format", "native format", format ); 142 } 143 144 return gc; 145 } 146 147 /** 148 * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this 149 * reader. 150 * 151 * @param parameters 152 * @throws IOException 153 */ 154 private GridCoverage performECW( GeneralParameterValueIm[] parameters ) 155 throws IOException { 156 157 BufferedImage bi = null; 158 CoverageOffering co = null; 159 Object[] o = null; 160 161 ECWReader ecwFile = null; 162 try { 163 String s = ( (File) source ).getName(); 164 ecwFile = new ECWReader( s ); 165 166 // get the requested dimension in pixels 167 int reqWidth = 0; 168 int reqHeight = 0; 169 for ( int i = 0; i < parameters.length; i++ ) { 170 OperationParameterIm op = (OperationParameterIm) parameters[i].getDescriptor(); 171 String name = op.getName(); 172 if ( name.equalsIgnoreCase( "WIDTH" ) ) { 173 Object vo = op.getDefaultValue(); 174 reqWidth = ( (Integer) vo ).intValue(); 175 } else if ( name.equalsIgnoreCase( "HEIGHT" ) ) { 176 Object vo = op.getDefaultValue(); 177 reqHeight = ( (Integer) vo ).intValue(); 178 } 179 } 180 181 // calculate image region of interest 182 o = getECWImageRegion( reqWidth, reqHeight ); 183 Envelope envl = (Envelope) o[1]; 184 185 Rectangle rect = (Rectangle) o[0]; 186 bi = ecwFile.getBufferedImage( envl, rect.width, rect.height ); 187 188 // create a coverage description that matches the sub image (coverage) 189 // for this a new LonLatEnvelope must be set 190 co = (CoverageOffering) description.clone(); 191 co.setLonLatEnvelope( (LonLatEnvelope) o[2] ); 192 193 } catch ( Exception e ) { 194 e.printStackTrace(); 195 throw new IOException( StringTools.stackTraceToString( e ) ); 196 } finally { 197 // free the ECW cache memory 198 if ( ecwFile != null ) { 199 ecwFile.close(); 200 } 201 } 202 203 return new ImageGridCoverage( co, (Envelope) o[1], bi ); 204 205 } 206 207 /** 208 * performs the creation of a <tt>ImageGridCoverage</tt> from the source assigned to this 209 * reader. 210 * 211 * @throws IOException 212 * @throws ParameterNotFoundException 213 */ 214 private GridCoverage performImage() 215 throws ParameterNotFoundException, IOException { 216 217 BufferedImage bi = readImage(); 218 219 // get image rectangle of interrest, envelope and lonlatenvelope 220 Object[] o = getImageRegion( bi.getWidth(), bi.getHeight() ); 221 Rectangle rect = (Rectangle) o[0]; 222 // return null if the result GC would have a width or height of zero 223 if ( rect.width == 0 || rect.height == 0 ) { 224 return null; 225 } 226 bi = bi.getSubimage( rect.x, rect.y, rect.width, rect.height ); 227 228 // create a coverage description that matches the sub image (coverage) 229 // for this a new LonLatEnvelope must be set 230 CoverageOffering co = (CoverageOffering) description.clone(); 231 co.setLonLatEnvelope( (LonLatEnvelope) o[2] ); 232 233 return new ImageGridCoverage( co, (Envelope) o[1], bi ); 234 } 235 236 /** 237 * reads an image from its source 238 * 239 * @throws IOException 240 */ 241 private BufferedImage readImage() 242 throws IOException { 243 BufferedImage bi = null; 244 if ( source.getClass() == File.class ) { 245 String s = ( (File) source ).getName(); 246 String tmp = s.toLowerCase(); 247 if ( tmp.startsWith( "file:" ) ) { 248 tmp = s.substring( 6, s.length() ); 249 bi = ImageUtils.loadImage( new java.io.File( tmp ) ); 250 } else if ( tmp.startsWith( "http:" ) ) { 251 bi = ImageUtils.loadImage( new URL( s ) ); 252 } else { 253 bi = ImageUtils.loadImage( new java.io.File( s ) ); 254 } 255 } else { 256 bi = ImageUtils.loadImage( (InputStream) source ); 257 } 258 return bi; 259 } 260 261 /** 262 * Return the SRS code of our native SRS. 263 */ 264 private String getNativeSRSCode() { 265 CodeList[] cl = description.getSupportedCRSs().getNativeSRSs(); 266 return cl[0].getCodes()[0]; 267 } 268 269 /** 270 * return the LonLatEnvelope of the entire image in "EPSG:4326" 271 */ 272 private Envelope getLLEAsEnvelope() { 273 String code = getNativeSRSCode(); 274 LonLatEnvelope lle = description.getLonLatEnvelope(); 275 Envelope tmp = GeometryFactory.createEnvelope( lle.getMin().getX(), lle.getMin().getY(), lle.getMax().getX(), 276 lle.getMax().getY(), null ); 277 try { 278 if ( !code.equals( "EPSG:4326" ) ) { 279 GeoTransformer trans = new GeoTransformer( code ); 280 tmp = trans.transform( tmp, "EPSG:4326" ); 281 } 282 } catch ( Exception e ) { 283 LOGGER.logError( StringTools.stackTraceToString( e ) ); 284 } 285 286 return tmp; 287 } 288 289 /** 290 * Calculate the rectangle that belongs to the given destination envelope in the given source 291 * image. 292 */ 293 private Object[] calcRegionRectangle( Envelope dstenv, Envelope srcenv, double srcwidth, double srcheight ) { 294 GeoTransform gt = new WorldToScreenTransform( srcenv.getMin().getX(), srcenv.getMin().getY(), 295 srcenv.getMax().getX(), srcenv.getMax().getY(), 0, 0, 296 srcwidth - 1, srcheight - 1 ); 297 298 int minx = (int) Math.round( gt.getDestX( dstenv.getMin().getX() ) ); 299 int miny = (int) Math.round( gt.getDestY( dstenv.getMax().getY() ) ); 300 int maxx = (int) Math.round( gt.getDestX( dstenv.getMax().getX() ) ); 301 int maxy = (int) Math.round( gt.getDestY( dstenv.getMin().getY() ) ); 302 Rectangle rect = new Rectangle( minx, miny, maxx - minx + 1, maxy - miny + 1 ); 303 LonLatEnvelope lonLatEnvelope = calcLonLatEnvelope( dstenv, getNativeSRSCode() ); 304 305 return new Object[] { rect, dstenv, lonLatEnvelope }; 306 } 307 308 /** 309 * returns the region of the source image that intersects with the GridCoverage to be created as 310 * Rectangle as well as the Envelope of the region in the native CRS and the LonLatEnvelope of 311 * this region. 312 * 313 * @param imgwidth 314 * width of the source image 315 * @param imgheight 316 * height of the source image 317 * @return the region of the source image that intersects with the GridCoverage to be created as 318 * Rectangle as well as the Envelope of the region in the native CRS and the 319 * LonLatEnvelope of this region. 320 */ 321 private Object[] getImageRegion( int imgwidth, int imgheight ) { 322 323 Envelope imgenvelope = getLLEAsEnvelope(); 324 Envelope dstenvelope = envelope.createIntersection( imgenvelope ); 325 return calcRegionRectangle( dstenvelope, imgenvelope, imgwidth, imgheight ); 326 } 327 328 /** 329 * returns 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 LonLatEnvelope of 331 * this region. For ECW files these values reflect exactly the desired result, as the library 332 * will cut and sample the source image during read. 333 * 334 * @param reqwidth 335 * width of the requested region 336 * @param reqheight 337 * height of the requested region 338 * @return the region of the source image that intersects with the GridCoverage to be created as 339 * Rectangle as well as the Envelope of the region in the native CRS and the 340 * LonLatEnvelope of this region. 341 */ 342 private Object[] getECWImageRegion( int reqwidth, int reqheight ) { 343 344 Envelope imgenvelope = getLLEAsEnvelope(); 345 Envelope dstenvelope = envelope.createIntersection( imgenvelope ); 346 347 // In case of ECW files, we calculate the rectangle according 348 // to the desired result, not according to the source image, 349 // as clipping and sampling will be done by the ECW library. 350 // Hence dstenvelope and srcenvelope are the same and width/height 351 // must be clipped with our grid-cell (if necessary). 352 double dWidth = ( dstenvelope.getWidth() * reqwidth ) / envelope.getWidth(); 353 double dHeight = ( dstenvelope.getHeight() * reqheight ) / envelope.getHeight(); 354 return calcRegionRectangle( dstenvelope, dstenvelope, dWidth, dHeight ); 355 } 356 357 /** 358 * Allows any resources held by this object to be released. The result of calling any other 359 * method subsequent to a call to this method is undefined. It is important for applications to 360 * call this method when they know they will no longer be using this 361 * <code>GridCoverageReader</code>. Otherwise, the reader may continue to hold on to 362 * resources indefinitely. 363 * 364 * @throws IOException 365 * if an error occured while disposing resources (for example while closing a file). 366 */ 367 public void dispose() 368 throws IOException { 369 if ( source instanceof InputStream ) { 370 ( (InputStream) source ).close(); 371 } 372 } 373 374 }