    001    //$HeadURL: svn+ssh://developername@svn.wald.intevation.org/deegree/base/trunk/src/org/deegree/tools/raster/Text2Tiff.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.ogcwebservices.wms.dataaccess;
    037
    038    import java.awt.Color;
    039    import java.awt.Graphics;
    040    import java.awt.Transparency;
    041    import java.awt.color.ColorSpace;
    042    import java.awt.image.BufferedImage;
    043    import java.awt.image.ColorModel;
    044    import java.awt.image.ComponentColorModel;
    045    import java.awt.image.DataBuffer;
    046    import java.awt.image.WritableRaster;
    047    import java.io.IOException;
    048    import java.io.InputStream;
    049    import java.net.URL;
    050    import java.util.ArrayList;
    051    import java.util.HashMap;
    052    import java.util.Hashtable;
    053    import java.util.Iterator;
    054    import java.util.List;
    055    import java.util.Map;
    056    import java.util.Properties;
    057
    058    import org.apache.commons.httpclient.HttpClient;
    059    import org.apache.commons.httpclient.methods.PostMethod;
    060    import org.apache.commons.httpclient.methods.StringRequestEntity;
    061    import org.deegree.datatypes.QualifiedName;
    062    import org.deegree.datatypes.values.Interval;
    063    import org.deegree.datatypes.values.TypedLiteral;
    064    import org.deegree.datatypes.values.Values;
    065    import org.deegree.framework.log.ILogger;
    066    import org.deegree.framework.log.LoggerFactory;
    067    import org.deegree.framework.util.FileUtils;
    068    import org.deegree.framework.util.StringTools;
    069    import org.deegree.framework.util.TimeTools;
    070    import org.deegree.framework.xml.XMLFragment;
    071    import org.deegree.graphics.transformation.GeoTransform;
    072    import org.deegree.graphics.transformation.WorldToScreenTransform;
    073    import org.deegree.io.quadtree.IndexException;
    074    import org.deegree.io.quadtree.MemPointQuadtree;
    075    import org.deegree.io.quadtree.Quadtree;
    076    import org.deegree.model.crs.GeoTransformer;
    077    import org.deegree.model.feature.Feature;
    078    import org.deegree.model.feature.FeatureCollection;
    079    import org.deegree.model.feature.GMLFeatureCollectionDocument;
    080    import org.deegree.model.spatialschema.Envelope;
    081    import org.deegree.model.spatialschema.GeometryFactory;
    082    import org.deegree.model.spatialschema.Point;
    083    import org.deegree.ogcwebservices.wms.operation.GetFeatureInfo;
    084    import org.deegree.ogcwebservices.wms.operation.GetFeatureInfoResult;
    085    import org.deegree.ogcwebservices.wms.operation.GetLegendGraphic;
    086    import org.deegree.ogcwebservices.wms.operation.GetMap;
    087    import org.deegree.ogcwebservices.wms.operation.GetMapResult;
    088    import org.deegree.processing.raster.interpolation.DataTuple;
    089    import org.deegree.processing.raster.interpolation.InterpolationException;
    090    import org.deegree.processing.raster.interpolation.InverseDistanceToPower;
    091
    092    /**
    093     * This class reads Point data from a WFS and interpolates them into a raster (image) using deegree's inverse distant to
    094     * power implementation. The class expects a confguration file that looks like this:<br>
    095     *
    096     * <pre>
    097     * #URL des WFS, der einen FeatureType mit Punktdaten liefert
    098     * url=http://bfs.lat-lon.de/deegree-wfs/services
    099     * #Name des Properties, das die zu interpolierenden Werte enhält
    100     * z_value={http://www.deegree.org/app}:value
    101     * # Farbtiefe des Ergebnisbildes (sollte nicht geändert werden)
    102     * image-type=32
    103     * # potenz der Entfernung mit der ein Wert bei der Interpolation gewichtet wird
    104     * interpolate-power=2
    105     * # minimale anzahl von Werten, die im Suchradius enthalten sein müssen
    106     * interpolate-min-data=2
    107     * # maximale Anzahl von Werten, die zur Berechung einer Rasterzelle
    108     * # heran gezogen werden
    109     * interpolate-max-data=40
    110     * # Default Werte, wenn für eine Rasterzelle keine Interpolation durchgeführt
    111     * # werden kann
    112     * interpolate-no-value=0
    113     * # Radius der Suchellipse in x-Richtung in Prozent der Breite der Boundingbox
    114     * interpolate-radius-x=30
    115     * # Radius der Suchellipse in y-Richtung in Prozent der Breite der Boundingbox
    116     * interpolate-radius-y=30
    117     * # Wert um die die suchellipse in x-Richtung vergössert wird in  Prozent der
    118     * # Breite der Boundingbox falls nicht genügend Werte enthalten sind
    119     * interpolate-radius-increase-x=5
    120     * # Wert um die die suchellipse in y-Richtung vergössert wird in  Prozent der
    121     * # Breite der Boundingbox falls nicht genügend Werte enthalten sind
    122     * interpolate-radius-increase-y=5
    123     * # Bereich von Werten, die bei der Interpolation ignoriert werden (Fehlwerte)
    124     * interpolate-ignore-range=-99999,-9999
    125     * # Grösse des Buffers um die Bounddingbox (in % der Boundingbox) mit dem Daten
    126     * # vom WFS angefragt werden
    127     * buffer=20
    128     * mindata=10
    129     * # properties file for mapping z_values to colors
    130     * # example:
    131     * # &lt;0=0x000000
    132     * # 0-80=0xedf8b1
    133     * # 80-110=0xc7e9b4
    134     * # 110-140=0x7fcdbb
    135     * # 140-170=0x41b6c4
    136     * # 170-200=0x1d91c0
    137     * # 200-400=0x225ea8
    138     * # 400-600=0xc2c84
    139     * # &gt;600=0x8b008b
    140     * colorMapFile=./color.properties
    141     * # GetFeature request template
    142     * # known wildcards are:
    143     * # $xmin$ $ymin$ $xmax$ $ymax$ (filled with BBOX value of GetMap request)
    144     * # $time$ (TIME value of GetMap request)
    145     * GetFeatureTemplate=./getfeature_template.xml
    146     * # time stamp to be used if no time parameter is set in GetMap request
    147     * defaultTime=2009-11-03T12:30:00
    148     * # native CRS of data requested from WFS
    149     * nativeCRS=EPSG:4326
    150     * </pre>
    151     *
    152     *
    153     * @author <a href="mailto:schmitz@lat-lon.de">Andreas Schmitz</a>
    154     * @author last edited by: $Author: poth $
    155     *
    156     * @version $Revision: 1.1 $, $Date: 2009-05-29 09:30:58 $
    157     */
    158    public class ID2PInterpolation implements ExternalRasterDataAccess {
    159
    160        private static final ILogger LOG = LoggerFactory.getLogger( ID2PInterpolation.class );
    161
    162        // parameters
    163
    164        private boolean use32Bits = false;
    165
    166        // data
    167        private Quadtree<DataTuple> quadtree;
    168
    169        private BufferedImage image;
    170
    171        // interpolating options
    172        private double interpolatePower = 2;
    173
    174        private int interpolateMinData = 5;
    175
    176        private int interpolateMaxData = 20;
    177
    178        private double interpolateNoValue = 0;
    179
    180        private double interpolateRadiusX = 2;
    181
    182        private double interpolateRadiusY = 2;
    183
    184        private double interpolateRadiusIncreaseX = 0;
    185
    186        private double interpolateRadiusIncreaseY = 0;
    187
    188        private Values ignoreValues = null;
    189
    190        private GeoTransform gt;
    191
    192        private double buffer = 10;
    193
    194        private Properties prop;
    195
    196        private static Map<double[], Color> colorMap;
    197
    198        private GetMap getMap;
    199
    200        private URL configFileURL;
    201
    202        private String nativeCRS;
    203
    204        private void parseColorMap()
    205                                throws IOException {
    206            XMLFragment dummy = new XMLFragment();
    207            dummy.setSystemId( configFileURL );
    208            URL url = dummy.resolve( prop.getProperty( "colorMapFile" ) );
    209            Properties p = new Properties();
    210            p.load( url.openStream() );
    211            Iterator<Object> iter = p.keySet().iterator();
    212            colorMap = new HashMap<double[], Color>();
    213            while ( iter.hasNext() ) {
    214                String s = (String) iter.next();
    215                double[] d = null;
    216                if ( s.startsWith( "<" ) ) {
    217                    d = new double[2];
    218                    d[0] = -9E9;
    219                    d[1] = Double.parseDouble( s.substring( 1, s.length() ) );
    220                } else if ( s.startsWith( ">" ) ) {
    221                    d = new double[2];
    222                    d[0] = Double.parseDouble( s.substring( 1, s.length() ) );
    223                    d[1] = 9E9;
    224                } else {
    225                    d = StringTools.toArrayDouble( s, "-" );
    226                }
    227                colorMap.put( d, Color.decode( p.getProperty( s ) ) );
    228            }
    229        }
    230
    231        private void parseProperties() {
    232            List<Interval> intervals = new ArrayList<Interval>();
    233            nativeCRS = prop.getProperty( "nativeCRS" );
    234            use32Bits = prop.getProperty( "image-type" ).equals( "32" );
    235            interpolatePower = Double.parseDouble( prop.getProperty( "interpolate-power" ) );
    236            interpolateMinData = Integer.parseInt( prop.getProperty( "interpolate-min-data" ) );
    237            interpolateMaxData = Integer.parseInt( prop.getProperty( "interpolate-max-data" ) );
    238            interpolateNoValue = Double.parseDouble( prop.getProperty( "interpolate-no-value" ) );
    239            interpolateRadiusX = Double.parseDouble( prop.getProperty( "interpolate-radius-x" ) );
    240            interpolateRadiusY = Double.parseDouble( prop.getProperty( "interpolate-radius-y" ) );
    241            interpolateRadiusIncreaseX = Double.parseDouble( prop.getProperty( "interpolate-radius-increase-x" ) );
    242            interpolateRadiusIncreaseY = Double.parseDouble( prop.getProperty( "interpolate-radius-increase-y" ) );
    243            String tmp = prop.getProperty( "interpolate-ignore-range" );
    244            String[] ig = StringTools.toArray( tmp, ",;", false );
    245            TypedLiteral min = new TypedLiteral( ig[0], null );
    246            TypedLiteral max = new TypedLiteral( ig[1], null );
    247            Interval interval = new Interval( min, max, null, null, null );
    248            intervals.add( interval );
    249            buffer = Double.parseDouble( prop.getProperty( "buffer" ) );
    250        }
    251
    252        // creates the buffered image with the right size
    253        private void createImage() {
    254
    255            ColorModel ccm;
    256
    257            if ( use32Bits ) {
    258                image = new BufferedImage( getMap.getWidth(), getMap.getHeight(), BufferedImage.TYPE_INT_ARGB );
    259            } else {
    260                ccm = new ComponentColorModel( ColorSpace.getInstance( ColorSpace.CS_GRAY ), null, false, false,
    261                                               Transparency.OPAQUE, DataBuffer.TYPE_USHORT );
    262                WritableRaster wr = ccm.createCompatibleWritableRaster( getMap.getWidth(), getMap.getHeight() );
    263                image = new BufferedImage( ccm, wr, false, new Hashtable<Object, Object>() );
    264            }
    265        }
    266
    267        // inserts all values into the image
    268        private void insertValue( int x, int y, double val ) {
    269            Iterator<double[]> keys = colorMap.keySet().iterator();
    270            double[] d = keys.next();
    271            Color color = null;
    272            while ( !( val >= d[0] && val < d[1] ) ) {
    273                d = keys.next();
    274            }
    275            color = colorMap.get( d );
    276            try {
    277                image.setRGB( x, y, color.getRGB() );
    278            } catch ( Exception e ) {
    279                System.out.println( x + " " + y );
    280            }
    281        }
    282
    283        private int buildQuadtree( FeatureCollection fc )
    284                                throws IndexException {
    285
    286            Iterator<Feature> iterator = fc.iterator();
    287            double min = Double.MAX_VALUE;
    288            double max = Double.MIN_VALUE;
    289            String tmp = prop.getProperty( "z_value" );
    290            int count = 0;
    291            while ( iterator.hasNext() ) {
    292                Feature feat = iterator.next();
    293                Point point = (Point) feat.getDefaultGeometryPropertyValue();
    294                QualifiedName qn = new QualifiedName( tmp );
    295                Object o = feat.getDefaultProperty( qn ).getValue();
    296                if ( o != null ) {
    297                    Double zValue = Double.parseDouble( o.toString() );
    298                    point = GeometryFactory.createPoint( point.getX(), point.getY(), null );
    299                    quadtree.insert( new DataTuple( point.getX(), point.getY(), zValue.doubleValue() ), point );
    300                    if ( zValue < min ) {
    301                        min = zValue;
    302                    }
    303                    if ( zValue > max ) {
    304                        max = zValue;
    305                    }
    306                    count++;
    307                }
    308            }
    309            System.out.println( "min value : " + min );
    310            System.out.println( "max value : " + max );
    311            return count;
    312        }
    313
    314        private FeatureCollection readData()
    315                                throws Exception {
    316            XMLFragment dummy = new XMLFragment();
    317            dummy.setSystemId( configFileURL );
    318            URL url = dummy.resolve( prop.getProperty( "GetFeatureTemplate" ) );
    319
    320            String gf = FileUtils.readTextFile( url ).toString();
    321            Envelope temp = getMap.getBoundingBox().getBuffer( getMap.getBoundingBox().getWidth() / 100d * buffer );
    322            // transform GetMap BBOX into nativeCRS if required
    323            if ( !getMap.getSrs().equalsIgnoreCase( nativeCRS ) ) {
    324                GeoTransformer tr = new GeoTransformer( nativeCRS );
    325                temp = tr.transform( temp, getMap.getSrs(), true );
    326            }
    327            String singleValue = null;
    328            if ( getMap.getDimTime() != null && getMap.getDimTime().values.size() > 0 ) {
    329                singleValue = getMap.getDimTime().values.peek().value;
    330            } else {
    331                if ( prop.getProperty( "defaultDateTime" ) != null ) {
    332                    singleValue = prop.getProperty( "defaultDateTime" );
    333                } else {
    334                    singleValue = TimeTools.getISOFormattedTime();
    335                }
    336            }
    337            gf = StringTools.replace( gf, "$time$", singleValue, false );
    338            gf = StringTools.replace( gf, "$xmin$", Double.toString( temp.getMin().getX() ), false );
    339            gf = StringTools.replace( gf, "$ymin$", Double.toString( temp.getMin().getY() ), false );
    340            gf = StringTools.replace( gf, "$xmax$", Double.toString( temp.getMax().getX() ), false );
    341            gf = StringTools.replace( gf, "$ymax$", Double.toString( temp.getMax().getY() ), false );
    342            LOG.logDebug( "GetFeature Request: ", gf );
    343            HttpClient client = new HttpClient();
    344            PostMethod pm = new PostMethod( prop.getProperty( "url" ) );
    345            pm.setRequestEntity( new StringRequestEntity( gf ) );
    346            client.executeMethod( pm );
    347            GMLFeatureCollectionDocument doc = new GMLFeatureCollectionDocument();
    348            InputStream is = pm.getResponseBodyAsStream();
    349            doc.load( is, prop.getProperty( "url" ) );
    350            is.close();
    351            if ( LOG.getLevel() == ILogger.LOG_DEBUG ) {
    352                LOG.logDebug( "GetFeature Response: ", doc.getAsPrettyString() );
    353            }
    354            FeatureCollection fc = doc.parse();
    355            // transform feature if required into CRS requested
    356            if ( !getMap.getSrs().equalsIgnoreCase( nativeCRS ) ) {
    357                GeoTransformer tr = new GeoTransformer( getMap.getSrs() );
    358                fc = tr.transform( fc );
    359            }
    360            return fc;
    361        }
    362
    363        private void writeErrorMessage() {
    364            Graphics g = image.getGraphics();
    365            g.setColor( Color.WHITE );
    366            g.fillRect( 0, 0, getMap.getWidth(), getMap.getHeight() );
    367            g.setColor( Color.RED );
    368            g.drawString( "not enough values for interpolation available", 10, 50 );
    369            g.dispose();
    370        }
    371
    372        private void interpolate()
    373                                throws InterpolationException {
    374
    375            Envelope bbox = getMap.getBoundingBox();
    376            double scx = bbox.getWidth() * interpolateRadiusX / 100d;
    377            double scy = bbox.getHeight() * interpolateRadiusY / 100d;
    378            double iscx = bbox.getWidth() * interpolateRadiusIncreaseX / 100d;
    379            double iscy = bbox.getHeight() * interpolateRadiusIncreaseY / 100d;
    380
    381            InverseDistanceToPower interpolator = new InverseDistanceToPower( quadtree, ignoreValues, scx, scy, 0,
    382                                                                              interpolateMinData, interpolateMaxData,
    383                                                                              interpolateNoValue, iscx, iscy,
    384                                                                              interpolatePower );
    385
    386            int count = getMap.getWidth() * getMap.getHeight();
    387
    388            int counter = 0;
    389
    390            int interpolatedCounter = 0;
    391
    392            for ( int xipos = 0; xipos < getMap.getWidth(); ++xipos ) {
    393                for ( int yipos = 0; yipos < getMap.getHeight(); ++yipos ) {
    394                    double xpos = gt.getSourceX( xipos );
    395                    double ypos = gt.getSourceY( yipos );
    396                    double val = interpolator.calcInterpolatedValue( xpos, ypos, scx, scy );
    397                    insertValue( xipos, yipos, val );
    398                }
    399            }
    400
    401            System.out.println( counter + '/' + count + ", interpolated " + interpolatedCounter + " values" );
    402        }
    403
    404        /*
    405         * (non-Javadoc)
    406         *
    407         * @see
    408         * org.deegree.ogcwebservices.wms.dataaccess.ExternalRasterDataAccess#perform(org.deegree.ogcwebservices.wms.operation
    409         * .GetMap)
    410         */
    411        public GetMapResult perform( GetMap getMap )
    412                                throws Exception {
    413            this.getMap = getMap;
    414            Envelope bbox = getMap.getBoundingBox();
    415
    416            gt = new WorldToScreenTransform( bbox.getMin().getX(), bbox.getMin().getY(), bbox.getMax().getX(),
    417                                             bbox.getMax().getY(), 0, 0, getMap.getWidth() - 1, getMap.getHeight() - 1 );
    418
    419            FeatureCollection fc = readData();
    420            System.out.println( fc.getBoundedBy() );
    421            quadtree = new MemPointQuadtree<DataTuple>( fc.getBoundedBy() );
    422
    423            int count = buildQuadtree( fc );
    424
    425            createImage();
    426
    427            if ( count >= Integer.parseInt( prop.getProperty( "mindata" ) ) ) {
    428                interpolate();
    429            } else {
    430                writeErrorMessage();
    431            }
    432
    433            System.out.println( "Done." );
    434
    435            return new GetMapResult( getMap, image );
    436        }
    437
    438        /*
    439         * (non-Javadoc)
    440         *
    441         * @see
    442         * org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#perform(org.deegree.ogcwebservices.wms.operation
    443         * .GetFeatureInfo)
    444         */
    445        public GetFeatureInfoResult perform( GetFeatureInfo gfi ) {
    446            // TODO Auto-generated method stub
    447            return null;
    448        }
    449
    450        /*
    451         * (non-Javadoc)
    452         *
    453         * @see
    454         * org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#perform(org.deegree.ogcwebservices.wms.operation
    455         * .GetLegendGraphic)
    456         */
    457        public BufferedImage perform( GetLegendGraphic glg ) {
    458
    459            BufferedImage bi = new BufferedImage( 150, colorMap.size() * 25, BufferedImage.TYPE_4BYTE_ABGR );
    460            Iterator<double[]> iterator = colorMap.keySet().iterator();
    461            List<double[]> list = new ArrayList<double[]>( colorMap.size() );
    462
    463            while ( iterator.hasNext() ) {
    464                double[] ds = iterator.next();
    465                list.add( ds );
    466            }
    467
    468            for ( int i = list.size() - 1; 0 <= i; i-- ) {
    469                for ( int j = 0; j < i; j++ ) {
    470                    if ( list.get( j + 1 )[0] < list.get( j )[0] ) {
    471                        double[] ds = list.get( j + 1 );
    472                        list.set( j + 1, list.get( j ) );
    473                        list.set( j, ds );
    474                    }
    475                }
    476            }
    477
    478            int i = 0;
    479            Graphics g = bi.getGraphics();
    480            for ( double[] ds : list ) {
    481                Color color = colorMap.get( ds );
    482                g.setColor( color );
    483                g.fillRect( 2, 2 + i * 25, 20, 20 );
    484                g.setColor( Color.BLACK );
    485                g.drawRect( 2, 2 + i * 25, 20, 20 );
    486                g.drawString( Double.toString( ds[0] ) + " - " + Double.toString( ds[1] ), 25, 17 + i * 25 );
    487                i++;
    488            }
    489            g.dispose();
    490            return bi;
    491
    492        }
    493
    494        /*
    495         * (non-Javadoc)
    496         *
    497         * @see org.deegree.ogcwebservices.wms.dataaccess.ExternalDataAccess#setConfigurationFile(java.net.URL)
    498         */
    499        public void setConfigurationFile( URL url )
    500                                throws IOException {
    501            this.configFileURL = url;
    502            prop = new Properties();
    503            prop.load( url.openStream() );
    504            parseProperties();
    505            parseColorMap();
    506        }
    507
    508    }