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    }