001 // $HeadURL: http://svn.wald.intevation.org/svn/deegree/base/trunk/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 }