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 }