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