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 * # <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 * # >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 }