001    // $HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.3_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    }