001 // $HeadURL: svn+ssh://jwilden@svn.wald.intevation.org/deegree/base/branches/2.5_testing/src/org/deegree/model/coverage/grid/AbstractGridCoverage.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.Graphics; 039 import java.awt.Rectangle; 040 import java.awt.image.BufferedImage; 041 import java.awt.image.DataBuffer; 042 import java.awt.image.Raster; 043 import java.awt.image.renderable.ParameterBlock; 044 import java.io.IOException; 045 import java.io.InputStream; 046 import java.util.Properties; 047 048 import javax.media.jai.InterpolationNearest; 049 import javax.media.jai.JAI; 050 import javax.media.jai.RenderedOp; 051 052 import org.deegree.framework.log.ILogger; 053 import org.deegree.framework.log.LoggerFactory; 054 import org.deegree.framework.util.BootLogger; 055 import org.deegree.graphics.transformation.GeoTransform; 056 import org.deegree.graphics.transformation.WorldToScreenTransform; 057 import org.deegree.model.coverage.AbstractCoverage; 058 import org.deegree.model.coverage.Coverage; 059 import org.deegree.model.crs.CoordinateSystem; 060 import org.deegree.model.spatialschema.Envelope; 061 import org.deegree.ogcwebservices.wcs.configuration.Extension; 062 import org.deegree.ogcwebservices.wcs.describecoverage.CoverageOffering; 063 import org.deegree.processing.raster.converter.Image2RawData; 064 065 /** 066 * Represent the basic implementation which provides access to grid coverage data. A <code>GC_GridCoverage</code> 067 * implementation may provide the ability to update grid values. 068 * 069 * @author <a href="mailto:poth@lat-lon.de">Andreas Poth</a> 070 * @version 2.11.2002 071 */ 072 073 public abstract class AbstractGridCoverage extends AbstractCoverage implements GridCoverage { 074 075 private static final long serialVersionUID = 7719709130950357397L; 076 077 private static final ILogger LOG = LoggerFactory.getLogger( AbstractGridCoverage.class ); 078 079 private final GridGeometry gridGeometry = null; 080 081 private boolean isEditable = false; 082 083 protected static float offset; 084 085 protected static float scaleFactor; 086 087 static { 088 // 16 bit coverage probably does not contain original values but scaled values 089 // with an offset to enable handling of float values. For correct handling of 090 // these coverages offset and scale factor must be known 091 InputStream is = ShortGridCoverage.class.getResourceAsStream( "16bit.properties" ); 092 Properties props = new Properties(); 093 try { 094 props.load( is ); 095 } catch ( IOException e ) { 096 BootLogger.logError( e.getMessage(), e ); 097 } 098 offset = Float.parseFloat( props.getProperty( "offset" ) ); 099 scaleFactor = Float.parseFloat( props.getProperty( "scaleFactor" ) ); 100 } 101 102 /** 103 * @param coverageOffering 104 * @param envelope 105 */ 106 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope ) { 107 super( coverageOffering, envelope ); 108 } 109 110 /** 111 * @param coverageOffering 112 * @param sources 113 * @param envelope 114 */ 115 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope, Coverage[] sources ) { 116 super( coverageOffering, envelope, sources ); 117 } 118 119 /** 120 * 121 * @param coverageOffering 122 * @param envelope 123 * @param isEditable 124 */ 125 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope, boolean isEditable ) { 126 super( coverageOffering, envelope ); 127 this.isEditable = isEditable; 128 } 129 130 /** 131 * 132 * @param coverageOffering 133 * @param envelope 134 * @param crs 135 * @param isEditable 136 */ 137 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope, CoordinateSystem crs, 138 boolean isEditable ) { 139 super( coverageOffering, envelope, null, crs ); 140 this.isEditable = isEditable; 141 } 142 143 /** 144 * 145 * @param coverageOffering 146 * @param envelope 147 * @param sources 148 * @param isEditable 149 */ 150 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope, Coverage[] sources, 151 boolean isEditable ) { 152 super( coverageOffering, envelope, sources ); 153 this.isEditable = isEditable; 154 } 155 156 /** 157 * 158 * @param coverageOffering 159 * @param envelope 160 * @param sources 161 * @param crs 162 * @param isEditable 163 */ 164 public AbstractGridCoverage( CoverageOffering coverageOffering, Envelope envelope, Coverage[] sources, 165 CoordinateSystem crs, boolean isEditable ) { 166 super( coverageOffering, envelope, sources, crs ); 167 this.isEditable = isEditable; 168 } 169 170 /** 171 * Returns <code>true</code> if grid data can be edited. 172 * 173 * @return <code>true</code> if grid data can be edited. 174 */ 175 public boolean isDataEditable() { 176 return isEditable; 177 } 178 179 /** 180 * Information for the grid coverage geometry. Grid geometry includes the valid range of grid coordinates and the 181 * georeferencing. 182 * 183 * @return the information for the grid coverage geometry. 184 * 185 */ 186 public GridGeometry getGridGeometry() { 187 return gridGeometry; 188 } 189 190 /** 191 * this is a deegree convenience method which returns the source image of an <tt>ImageGridCoverage</tt>. In procipal 192 * the same can be done with the getRenderableImage(int xAxis, int yAxis) method. but creating a 193 * <tt>RenderableImage</tt> image is very slow. 194 * 195 * @param xAxis 196 * Dimension to use for the <var>x</var> axis. 197 * @param yAxis 198 * Dimension to use for the <var>y</var> axis. 199 * @return the image 200 */ 201 abstract public BufferedImage getAsImage( int xAxis, int yAxis ); 202 203 protected BufferedImage paintImage( BufferedImage targetImg, Envelope targetEnv, BufferedImage sourceImg, 204 Envelope sourceEnv ) { 205 return this.paintImage( targetImg, null, targetEnv, sourceImg, sourceEnv ); 206 } 207 208 /** 209 * renders a source image onto the correct position of a target image according to threir geographic extends 210 * (Envelopes). 211 * 212 * @param targetImg 213 * @param targetEnv 214 * @param sourceImg 215 * @param sourceEnv 216 * @return targetImg with sourceImg rendered on 217 */ 218 protected BufferedImage paintImage( BufferedImage targetImg, float[][] data, Envelope targetEnv, 219 BufferedImage sourceImg, Envelope sourceEnv ) { 220 221 int targetImgWidth = targetImg.getWidth(); 222 int targetImgHeight = targetImg.getHeight(); 223 GeoTransform gt = new WorldToScreenTransform( targetEnv.getMin().getX(), targetEnv.getMin().getY(), 224 targetEnv.getMax().getX(), targetEnv.getMax().getY(), 0, 0, 225 targetImgWidth - 1, targetImgHeight - 1 ); 226 227 // border pixel coordinates of the source image in the target coordinate system 228 int x1 = (int) Math.round( gt.getDestX( sourceEnv.getMin().getX() ) ); 229 int y1 = (int) Math.round( gt.getDestY( sourceEnv.getMax().getY() ) ); 230 int x2 = (int) Math.round( gt.getDestX( sourceEnv.getMax().getX() ) ); 231 int y2 = (int) Math.round( gt.getDestY( sourceEnv.getMin().getY() ) ); 232 233 if ( Math.abs( x2 - x1 ) <= 0 && Math.abs( y2 - y1 ) <= 0 || sourceImg.getWidth() == 1 234 || sourceImg.getHeight() == 1 ) { 235 // nothing to copy, return targetImg unchanged 236 return targetImg; 237 } 238 239 sourceImg = scale( sourceImg, targetImg, sourceEnv, targetEnv ); 240 241 int srcPs = sourceImg.getColorModel().getPixelSize(); 242 int targetPs = targetImg.getColorModel().getPixelSize(); 243 if ( targetPs == 16 && srcPs == 16 ) { 244 LOG.logDebug( "Painting from 16bpp to 16bpp" ); 245 paintImageT16S16( targetImg, data, sourceImg, x1, y1, x2, y2 ); 246 } else if ( targetPs == 16 && srcPs == 32 ) { 247 LOG.logDebug( "Painting from 32bpp to 16bpp" ); 248 paintImageT16S32( targetImg, sourceImg, x1, y1, x2, y2 ); 249 } else if ( targetPs == 32 && srcPs == 16 ) { 250 LOG.logDebug( "Painting from 16bpp to 32bpp" ); 251 paintImageT32S16( targetImg, sourceImg, x1, y1, x2, y2 ); 252 } else { 253 LOG.logDebug( "Painting 'default'" ); 254 paintImageDefault( targetImg, sourceImg, x1, y1, x2, y2 ); 255 } 256 257 return targetImg; 258 } 259 260 /** 261 * Paint overlapping area from sourceImg into targetImg. This method works with 16bit target pixel and 32bit source 262 * pixel. 263 * 264 * @param targetImg 265 * the target image. 266 * @param sourceImg 267 * the source image. 268 * @param xt1 269 * x-coordinate of the first Pixel of sourceImg in targetImg 270 * @param yt1 271 * y-coordinate of the first Pixel of sourceImg in targetImg 272 * @param xt2 273 * x-coordinate of the last Pixel of sourceImg in targetImg 274 * @param yt2 275 * y-coordinate of the last Pixel of sourceImg in targetImg 276 */ 277 private void paintImageT16S32( BufferedImage targetImg, BufferedImage sourceImg, int xt1, int yt1, int xt2, int yt2 ) { 278 279 assert sourceImg.getColorModel().getPixelSize() == 32; 280 assert targetImg.getColorModel().getPixelSize() == 16; 281 282 // locals for performance reasons 283 int targetImgWidth = targetImg.getWidth(); 284 int targetImgHeight = targetImg.getHeight(); 285 int sourceImgWidth = sourceImg.getWidth(); 286 int sourceImgHeight = sourceImg.getHeight(); 287 288 Raster raster = targetImg.getData(); 289 DataBuffer targetBuffer = raster.getDataBuffer(); 290 291 // i is the running x coordinate of the overlapping area in the source image 292 // j is the running y coordinate of the overlapping area in the source image 293 // targetX is the running x coordinate of the overlapping area in the target image 294 // targetY is the running y coordinate of the overlapping area in the target image 295 Extension extension = getCoverageOffering().getExtension(); 296 float scaleFactor = (float) extension.getScaleFactor(); 297 float offset = (float) extension.getOffset(); 298 for ( int i = Math.max( 0, sourceImgWidth - 1 - xt2 ); i <= Math.min( targetImgWidth - 1 - xt1, 299 sourceImgWidth - 1 ); i++ ) { 300 int targetX = xt1 + i; 301 for ( int j = Math.max( 0, sourceImgHeight - 1 - yt2 ); j <= Math.min( targetImgHeight - 1 - yt1, 302 sourceImgHeight - 1 ); j++ ) { 303 int targetY = yt1 + j; 304 int targetPos = targetImgWidth * targetY + targetX; 305 int v = sourceImg.getRGB( i, j ); 306 float f = Float.intBitsToFloat( v ) * scaleFactor + offset; 307 targetBuffer.setElem( targetPos, Math.round( f ) ); 308 } 309 } 310 targetImg.setData( Raster.createRaster( targetImg.getSampleModel(), targetBuffer, null ) ); 311 } 312 313 /** 314 * Paint overlapping area from sourceImg into targetImg. This method works with 32bit target pixel and 16bit source 315 * pixel. 316 * 317 * @param targetImg 318 * the target image. 319 * @param sourceImg 320 * the source image. 321 * @param xt1 322 * x-coordinate of the first Pixel of sourceImg in targetImg 323 * @param yt1 324 * y-coordinate of the first Pixel of sourceImg in targetImg 325 * @param xt2 326 * x-coordinate of the last Pixel of sourceImg in targetImg 327 * @param yt2 328 * y-coordinate of the last Pixel of sourceImg in targetImg 329 */ 330 private void paintImageT32S16( BufferedImage targetImg, BufferedImage sourceImg, int xt1, int yt1, int xt2, int yt2 ) { 331 332 assert sourceImg.getColorModel().getPixelSize() == 16; 333 assert targetImg.getColorModel().getPixelSize() == 32; 334 335 // locals for performance reasons 336 int targetImgWidth = targetImg.getWidth(); 337 int targetImgHeight = targetImg.getHeight(); 338 int sourceImgWidth = sourceImg.getWidth(); 339 int sourceImgHeight = sourceImg.getHeight(); 340 341 Raster raster = targetImg.getData(); 342 DataBuffer targetBuffer = raster.getDataBuffer(); 343 // Rectangle targetRect = raster.getBounds(); // I assume that the created image ALWAYS has a 0, 0, x, y rect. 344 // If not, use this one... 345 raster = sourceImg.getData(); 346 DataBuffer srcBuffer = raster.getDataBuffer(); 347 Rectangle srcRect = raster.getBounds(); // need to use this for data access, as it may not be 0, 0, x, y but -5, 348 // -29, x+5, y+29 or something and the srcPos would be messed up 349 350 // i is the running x coordinate of the overlapping area in the source image 351 // j is the running y coordinate of the overlapping area in the source image 352 // targetX is the running x coordinate of the overlapping area in the target image 353 // targetY is the running y coordinate of the overlapping area in the target image 354 Extension extension = getCoverageOffering().getExtension(); 355 float scaleFactor = (float) extension.getScaleFactor(); 356 float offset = (float) extension.getOffset(); 357 358 int dstX = Math.max( 0, sourceImgWidth - 1 - xt2 ); 359 int maxWidth = Math.min( targetImgWidth - xt1 - 1, sourceImgWidth - 1 ); 360 int dstY = Math.max( 0, sourceImgHeight - 1 - yt2 ); 361 int maxHeight = Math.min( targetImgHeight - yt1 - 1, sourceImgHeight - 1 ); 362 363 for ( int i = dstX; i <= maxWidth; i++ ) { 364 int targetX = xt1 + i; 365 for ( int j = dstY; j <= maxHeight; j++ ) { 366 int targetY = yt1 + j; 367 int srcPos = srcRect.width * j + i - srcRect.x - srcRect.y * srcRect.width; 368 int targetPos = targetImgWidth * targetY + targetX; 369 370 float f = srcBuffer.getElem( srcPos ) / scaleFactor - offset; 371 targetBuffer.setElem( targetPos, Float.floatToIntBits( f ) ); 372 } 373 } 374 targetImg.setData( Raster.createRaster( targetImg.getSampleModel(), targetBuffer, null ) ); 375 } 376 377 /** 378 * Paint overlapping area from sourceImg into targetImg. This method works with 16bit target pixel and 16bit source 379 * pixel. 380 * 381 * @param targetImg 382 * the target image. 383 * @param data 384 * @param sourceImg 385 * the source image. 386 * @param xt1 387 * x-coordinate of the first Pixel of sourceImg in targetImg 388 * @param yt1 389 * y-coordinate of the first Pixel of sourceImg in targetImg 390 * @param xt2 391 * x-coordinate of the last Pixel of sourceImg in targetImg 392 * @param yt2 393 * y-coordinate of the last Pixel of sourceImg in targetImg 394 */ 395 private void paintImageT16S16( BufferedImage targetImg, float[][] data, BufferedImage sourceImg, int xt1, int yt1, 396 int xt2, int yt2 ) { 397 assert sourceImg.getColorModel().getPixelSize() == 16; 398 assert targetImg.getColorModel().getPixelSize() == 16; 399 400 // locals for performance reasons 401 int targetImgWidth = targetImg.getWidth(); 402 int targetImgHeight = targetImg.getHeight(); 403 int sourceImgWidth = sourceImg.getWidth(); 404 int sourceImgHeight = sourceImg.getHeight(); 405 406 Raster raster = targetImg.getData(); 407 DataBuffer targetBuffer = raster.getDataBuffer(); 408 raster = sourceImg.getData(); 409 float[][] newData = null; 410 Image2RawData i2r = new Image2RawData( sourceImg, 1f / scaleFactor, -1 * offset ); 411 newData = i2r.parse(); 412 413 // i is the running x coordinate of the overlapping area in the source image 414 // j is the running y coordinate of the overlapping area in the source image 415 // targetX is the running x coordinate of the overlapping area in the target image 416 // targetY is the running y coordinate of the overlapping area in the target image 417 for ( int i = Math.max( 0, sourceImgWidth - xt2 - 1 ); i <= Math.min( targetImgWidth - xt1 - 1, 418 sourceImgWidth - 1 ); i++ ) { 419 int targetX = xt1 + i; 420 for ( int j = Math.max( 0, sourceImgHeight - yt2 - 1 ); j <= Math.min( targetImgHeight - yt1 - 1, 421 sourceImgHeight - 1 ); j++ ) { 422 int targetY = yt1 + j; 423 // int v = srcBuffer.getElem( srcPos ); 424 // targetBuffer.setElem( targetPos, v ); 425 data[targetY][targetX] = newData[j][i]; 426 } 427 } 428 targetImg.setData( Raster.createRaster( targetImg.getSampleModel(), targetBuffer, null ) ); 429 } 430 431 /** 432 * Paint overlapping area from sourceImg into targetImg. This method works with every combination of pixel size in 433 * the target and source image. 434 * 435 * @param targetImg 436 * @param sourceImg 437 * @param xt1 438 * x-coordinate of the first Pixel of sourceImg in targetImg 439 * @param yt1 440 * y-coordinate of the first Pixel of sourceImg in targetImg 441 * @param xt2 442 * x-coordinate of the last Pixel of sourceImg in targetImg 443 * @param yt2 444 * y-coordinate of the last Pixel of sourceImg in targetImg 445 */ 446 private void paintImageDefault( BufferedImage targetImg, BufferedImage sourceImg, int xt1, int yt1, int xt2, int yt2 ) { 447 // int xs1 = max( 0, sourceImg.getWidth() - 1 - xt2 ); 448 // int xs2 = min( targetImg.getWidth() - 1 - xt1, sourceImg.getWidth() - 1 ); 449 // int ys1 = max( 0, sourceImg.getHeight() - 1 - yt2 ); 450 // int ys2 = min( targetImg.getHeight() - 1 - yt1, sourceImg.getHeight() - 1 ); 451 // int copyWidth = ( xs2 - xs1 + 1 ); 452 // int copyHeight = ( ys2 - ys1 + 1 ); 453 // int[] rgbs = new int[copyWidth * copyHeight]; 454 // sourceImg.getRGB( xs1, ys1, copyWidth, copyHeight, rgbs, 0, copyWidth ); 455 // targetImg.setRGB( xt1, yt1, copyWidth, copyHeight, rgbs, 0, copyWidth ); 456 457 Graphics g = targetImg.getGraphics(); 458 /* 459 * if ( sourceImg.getColorModel().getPixelSize() == 32 && targetImg.getColorModel().getPixelSize() == 32 ) { ( 460 * (Graphics2D) g ).setComposite( AlphaComposite.DstOver ); } 461 */ 462 g.drawImage( sourceImg, xt1, yt1, sourceImg.getWidth(), sourceImg.getHeight(), null ); 463 } 464 465 private BufferedImage scale( BufferedImage sourceImg, BufferedImage targetImg, Envelope srcEnv, Envelope trgEnv ) { 466 double sw = sourceImg.getWidth() - 1; 467 if ( sourceImg.getWidth() == 1 ) { 468 sw = 1f; 469 } 470 double sh = sourceImg.getHeight() - 1; 471 if ( sourceImg.getHeight() == 1 ) { 472 sh = 1f; 473 } 474 double srcXres = srcEnv.getWidth() / sw; 475 double srcYres = srcEnv.getHeight() / sh; 476 477 double tw = targetImg.getWidth() - 1; 478 if ( targetImg.getWidth() == 1 ) { 479 tw = 1; 480 } 481 double th = targetImg.getHeight() - 1; 482 if ( targetImg.getHeight() == 1 ) { 483 th = 1; 484 } 485 double trgXres = trgEnv.getWidth() / tw; 486 double trgYres = trgEnv.getHeight() / th; 487 488 float sx = (float) ( srcXres / trgXres ); 489 float sy = (float) ( srcYres / trgYres ); 490 491 if ( ( sy < 0.9999 ) || ( sy > 1.0001 ) || ( sx < 0.9999 ) || ( sx > 1.0001 ) ) { 492 try { 493 ParameterBlock pb = new ParameterBlock(); 494 pb.addSource( sourceImg ); 495 496 LOG.logDebug( "Scale image: by factors: " + sx + ' ' + sy ); 497 pb.add( sx ); // The xScale 498 pb.add( sy ); // The yScale 499 pb.add( 0.0F ); // The x translation 500 pb.add( 0.0F ); // The y translation 501 pb.add( new InterpolationNearest() ); // The interpolation 502 // pb.add( new InterpolationBilinear() ); // The interpolation 503 // Create the scale operation 504 RenderedOp ro = JAI.create( "scale", pb, null ); 505 sourceImg = ro.getAsBufferedImage(); 506 } catch ( Exception e ) { 507 LOG.logDebug( e.getMessage(), e ); 508 } 509 } 510 return sourceImg; 511 } 512 }