001 //$HeadURL: svn+ssh://rbezema@svn.wald.intevation.org/deegree/base/tags/2.1/src/org/deegree/ogcwebservices/wps/execute/processes/Buffer.java $ 002 /*---------------- FILE HEADER ------------------------------------------ 003 004 This file is part of deegree. 005 Copyright (C) 2001-2007 by: 006 EXSE, Department of Geography, University of Bonn 007 http://www.giub.uni-bonn.de/exse/ 008 lat/lon GmbH 009 http://www.lat-lon.de 010 011 This library is free software; you can redistribute it and/or 012 modify it under the terms of the GNU Lesser General Public 013 License as published by the Free Software Foundation; either 014 version 2.1 of the License, or (at your option) any later version. 015 016 This library is distributed in the hope that it will be useful, 017 but WITHOUT ANY WARRANTY; without even the implied warranty of 018 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 019 Lesser General Public License for more details. 020 021 You should have received a copy of the GNU Lesser General Public 022 License along with this library; if not, write to the Free Software 023 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 024 025 Contact: 026 027 Andreas Poth 028 lat/lon GmbH 029 Aennchenstraße 19 030 53177 Bonn 031 Germany 032 E-Mail: poth@lat-lon.de 033 034 Prof. Dr. Klaus Greve 035 Department of Geography 036 University of Bonn 037 Meckenheimer Allee 166 038 53115 Bonn 039 Germany 040 E-Mail: greve@giub.uni-bonn.de 041 042 ---------------------------------------------------------------------------*/ 043 044 package org.deegree.ogcwebservices.wps.execute.processes; 045 046 import java.net.URI; 047 import java.net.URL; 048 import java.util.ArrayList; 049 import java.util.Iterator; 050 import java.util.List; 051 import java.util.Map; 052 053 import org.deegree.datatypes.Code; 054 import org.deegree.datatypes.QualifiedName; 055 import org.deegree.datatypes.Types; 056 import org.deegree.datatypes.values.TypedLiteral; 057 import org.deegree.framework.log.ILogger; 058 import org.deegree.framework.log.LoggerFactory; 059 import org.deegree.model.feature.Feature; 060 import org.deegree.model.feature.FeatureCollection; 061 import org.deegree.model.feature.FeatureFactory; 062 import org.deegree.model.feature.FeatureProperty; 063 import org.deegree.model.feature.schema.FeatureType; 064 import org.deegree.model.feature.schema.PropertyType; 065 import org.deegree.model.spatialschema.GeometryException; 066 import org.deegree.model.spatialschema.JTSAdapter; 067 import org.deegree.ogcwebservices.MissingParameterValueException; 068 import org.deegree.ogcwebservices.OGCWebServiceException; 069 import org.deegree.ogcwebservices.wps.describeprocess.InputDescription; 070 import org.deegree.ogcwebservices.wps.describeprocess.ProcessDescription; 071 import org.deegree.ogcwebservices.wps.execute.ComplexValue; 072 import org.deegree.ogcwebservices.wps.execute.ExecuteResponse; 073 import org.deegree.ogcwebservices.wps.execute.IOValue; 074 import org.deegree.ogcwebservices.wps.execute.OutputDefinition; 075 import org.deegree.ogcwebservices.wps.execute.OutputDefinitions; 076 import org.deegree.ogcwebservices.wps.execute.Process; 077 import org.deegree.ogcwebservices.wps.execute.ExecuteResponse.ProcessOutputs; 078 079 import com.vividsolutions.jts.geom.Geometry; 080 import com.vividsolutions.jts.operation.buffer.BufferOp; 081 082 /** 083 * Buffer.java 084 * 085 * Created on 09.03.2006. 23:42:39h 086 * 087 * This class describes an exemplary Process Implementation. The corresponding configuration 088 * document is '<root>\WEB-INF\conf\wps\processConfigs.xml'. Process configuration is described 089 * further inside the configuration document. 090 * 091 * The process implementor has to ensure, that the process implemented extends the abstract super 092 * class Process. 093 * 094 * This example process IS NOT intended to describe a best practice approach. In some cases 095 * simplifying assumptions have been made for sake of simplicity. 096 * 097 * @author <a href="mailto:christian@kiehle.org">Christian Kiehle</a> 098 * @author <a href="mailto:christian.heier@gmx.de">Christian Heier</a> 099 * 100 * @version 1.0. 101 * 102 * @since 2.0 103 */ 104 105 public class Buffer extends Process { 106 107 /** 108 * 109 * @param processDescription 110 */ 111 public Buffer( ProcessDescription processDescription ) { 112 super( processDescription ); 113 } 114 115 // define an ILogger for this class 116 private static final ILogger LOG = LoggerFactory.getLogger( Buffer.class ); 117 118 /** 119 * The provided buffer implementation relies on the Java Topology Suite 120 * <link>http://www.vividsolutions.com/jts/JTSHome.htm</link>. Buffering is an operation which 121 * in GIS is used to compute the area containing all points within a given distance of a 122 * Geometry. A buffer takes at least two inputs: 123 * 124 * 1.) The Geometry to buffer (point, linestring, polygon, etc.) 2.) The distance of the buffer. 125 * 126 * The provided Buffer defines two optional elements as well: 127 * 128 * 3.) The end cap style, which determines how the linework for the buffer polygon is 129 * constructed at the ends of linestrings. Possible styles are: a) CAP_ROUND The usual round end 130 * caps (integer value of 1) b) CAP_BUTT End caps are truncated flat at the line ends (integer 131 * value of 2) c) CAP_SQUARE End caps are squared off at the buffer distance beyond the line 132 * ends (integer value of 2) 133 * 134 * 4.) Since the exact buffer outline of a Geometry usually contains circular sections, the 135 * buffer must be approximated by the linear Geometry. The degree of approximation may be 136 * controlled by the user. This is done by specifying the number of quadrant segments used to 137 * approximate a quarter-circle. Specifying a larger number of segments results in a better 138 * approximation to the actual area, but also results in a larger number of line segments in the 139 * computed polygon. The default value is 8. 140 * 141 */ 142 143 /*********************************************************************************************** 144 * <wps:DataInputs> 145 **********************************************************************************************/ 146 147 /** 148 * The input section defines four elements: 1) BufferDistance (mandatory), wich will be mapped 149 * to 150 * 151 * @see <code>private int bufferDistance</code> 2) CapStyle (optional, when ommited, default 152 * value 1 will be used), which will be mapped to 153 * @see <code>private int capStyle</code> 3) ApproximationQuantization (optional, when 154 * ommited, default value 8 will be used), which will be mapped to 155 * @see <code>private int approximationQuantization</code> 4) InputGeometry (mandatory), which 156 * will be mapped to 157 * @see <code>private Object content</code> 158 * 159 * To illustrate the use, the first and fourth input Elements are included below. 160 * 161 */ 162 163 // "BufferDistance", "CapStyle", and "ApproximationQuantization" refer to 164 // the corresponding <ows:Identifier/> elements. 165 private static final String BUFFER_DISTANCE = "BufferDistance"; 166 167 private static final String CAP_STYLE = "CapStyle"; 168 169 private static final String APPROXIMATION_QUANTIZATION = "ApproximationQuantization"; 170 171 private static final String INPUT_GEOMETRY = "InputGeometry"; 172 173 /*********************************************************************************************** 174 * <wps:Input> <ows:Identifier>BufferDistance</ows:Identifier> <ows:Title>BufferDistance</ows:Title> 175 * <ows:Abstract>Width of Buffer</ows:Abstract> <wps:LiteralValue 176 * dataType="urn:ogc:def:dataType:OGC:0.0:Double" uom="urn:ogc:def:dataType:OGC:1.0:metre"> 50</wps:LiteralValue> 177 * </wps:Input> 178 **********************************************************************************************/ 179 180 // the required attributes for calculating a spatial buffer, initialized 181 // with default-values. 182 private int bufferDistance = 0; 183 184 private int capStyle = 1; 185 186 private int approximationQuantization = 8; 187 188 /** 189 * the content represents the <wps:ComplexValue/> Element in the ProcessInput section. This 190 * sample process is feeded with a feature collection, resulting in <wps:ComplexValue 191 * format="text/xml" encoding="UTF-8" 192 * schema="http://schemas.opengis.net/gml/3.0.0/base/gml.xsd"> <wfs:FeatureCollection 193 * xmlns:gml="http://www.opengis.net/gml" xmlns:wfs="http://www.opengis.net/wfs" 194 * xmlns:app="http://www.deegree.org/app" xmlns:xlink="http://www.w3.org/1999/xlink"> 195 * <gml:boundedBy> <gml:Envelope> <gml:pos>2581829.334 5660821.982</gml:pos> 196 * <gml:pos>2582051.078 5661086.442</gml:pos> </gml:Envelope> </gml:boundedBy> 197 * <gml:featureMember> <app:flurstuecke gml:id="ID_10208"> <app:gid></app:gid> <app:id></app:id> 198 * <app:rechtswert>2581969.20000000020</app:rechtswert> <app:hochwert>5660957.50000000000</app:hochwert> 199 * <app:datum></app:datum> <app:folie></app:folie> <app:objart></app:objart> 200 * <app:aliasfolie>Flurstuecke</app:aliasfolie> <app:aliasart>Flurstueck</app:aliasart> 201 * <app:alknr></app:alknr> <app:gemarkung></app:gemarkung> <app:flur></app:flur> 202 * <app:zaehler></app:zaehler> <app:nenner></app:nenner> <app:beschrift></app:beschrift> 203 * <app:the_geom> <gml:MultiPolygon srsName="EPSG:31466"> <gml:polygonMember> <gml:Polygon 204 * srsName="EPSG:31466"> <gml:outerBoundaryIs> <gml:LinearRing> <gml:coordinates cs="," 205 * decimal="." ts=" ">2581856.436,5660874.757 2581947.164,5660938.093 2581940.797,5660952.002 206 * 2581936.158,5660962.135 2581971.597,5660982.717 2581971.83,5660982.852 2581969.62,5660994.184 207 * 2581967.616,5661004.464 2581959.465,5661016.584 2581958.555,5661017.679 208 * 2581967.415,5661024.833 2581974.177,5661032.529 2582021.543,5661086.442 209 * 2582051.078,5661001.919 2582002.624,5660957.782 2581960.501,5660919.412 210 * 2581956.98,5660916.972 2581904.676,5660880.734 2581878.263,5660853.196 211 * 2581868.096,5660842.595 2581848.325,5660821.982 2581829.334,5660840.172 212 * 2581837.725,5660850.881 2581856.436,5660874.757</gml:coordinates> </gml:LinearRing> 213 * </gml:outerBoundaryIs> </gml:Polygon> </gml:polygonMember> </gml:MultiPolygon> 214 * </app:the_geom> </app:flurstuecke> </gml:featureMember> </wfs:FeatureCollection> 215 * </wps:ComplexValue> 216 * 217 */ 218 219 private Object content = null; 220 221 // Values for ProcessOutput, will be filled dynamically. 222 223 private Code identifier = null; 224 225 private String title = null; 226 227 private String _abstract = null; 228 229 private URL schema = null; 230 231 @SuppressWarnings("unused") 232 private URI uom = null; 233 234 private String format = null; 235 236 private URI encoding = null; 237 238 private ComplexValue complexValue = null; 239 240 private TypedLiteral literalValue = null; 241 242 /** 243 * (non-Javadoc) 244 * 245 * @see org.deegree.ogcwebservices.wps.execute.Process#execute(org.deegree.ogcwebservices.wps.execute.ExecuteRequest) 246 * 247 * This is the central method for implementing a process. A <code>Map<String,IOValue></code> 248 * serves as an input object. Each String represents the key (e.g. BufferDistance) which holds 249 * an IOValue as value (e.g. an object representing a complete <wps:Input> element with all 250 * corresponding sub-elements). The process implementation is responsible for retrieving all 251 * specified values according to the process configuration document. 252 * 253 * The method returns a <code>ProcessOutputs</code> object, which encapsulates the result of 254 * the process's operation. 255 * 256 */ 257 public ProcessOutputs execute( Map<String, IOValue> inputs, OutputDefinitions outputDefinitions ) 258 throws OGCWebServiceException { 259 LOG.entering(); 260 261 // delegate the read out of parameters to a private method 262 readValuesFromInputDefinedValues( inputs ); 263 264 // get configured ( = supported) inputs from processDescription 265 ProcessDescription.DataInputs configuredDataInputs = processDescription.getDataInputs(); 266 267 // delegate the read out of configured ( = supported ) inputs to a 268 // private method 269 readSupportedInputs( configuredDataInputs ); 270 271 // delegate the read out of configured outputs to a private method 272 readOutputDefinitions( outputDefinitions ); 273 274 // Define a processOutputs object 275 // validate, that data inputs correspond to process descritption 276 ProcessOutputs processOutputs = null; 277 boolean isValid = validate(); 278 if ( isValid ) { 279 processOutputs = process(); 280 281 } else { 282 LOG.logError( "Data input is invalid." ); 283 throw new OGCWebServiceException( "Buffer", "The configuraiton is invalid" ); 284 } 285 LOG.exiting(); 286 return processOutputs; 287 } 288 289 /** 290 * FIXME Assumes (simplified for the actual process) that only one output is defined. Reads the 291 * output definitions into local variables. 292 * 293 * @param outputDefinitions 294 */ 295 private void readOutputDefinitions( OutputDefinitions outputDefinitions ) { 296 List<OutputDefinition> outputDefinitionList = outputDefinitions.getOutputDefinitions(); 297 Iterator<OutputDefinition> outputDefinitionListIterator = outputDefinitionList.iterator(); 298 while ( outputDefinitionListIterator.hasNext() ) { 299 OutputDefinition outputDefinition = outputDefinitionListIterator.next(); 300 this._abstract = outputDefinition.getAbstract(); 301 this.title = outputDefinition.getTitle(); 302 this.identifier = outputDefinition.getIdentifier(); 303 this.schema = outputDefinition.getSchema(); 304 this.format = outputDefinition.getFormat(); 305 this.encoding = outputDefinition.getEncoding(); 306 this.uom = outputDefinition.getUom(); 307 } 308 } 309 310 /** 311 * Private method for assigning input values to local variables 312 * 313 * @param inputs 314 * @throws OGCWebServiceException 315 */ 316 private void readValuesFromInputDefinedValues( Map<String, IOValue> inputs ) 317 throws OGCWebServiceException { 318 319 // check for mandatory values 320 if ( null != inputs.get( BUFFER_DISTANCE ) ) { 321 IOValue ioBufferDistance = inputs.get( BUFFER_DISTANCE ); 322 TypedLiteral literalBuffer = ioBufferDistance.getLiteralValue(); 323 this.bufferDistance = Integer.parseInt( literalBuffer.getValue() ); 324 } else { 325 throw new MissingParameterValueException( getClass().getName(), 326 "The required Input Parameter BufferDistance is missing." ); 327 } 328 329 if ( null != inputs.get( INPUT_GEOMETRY ) ) { 330 IOValue ioGeometry = inputs.get( INPUT_GEOMETRY ); 331 ComplexValue complexGeometry = ioGeometry.getComplexValue(); 332 this.content = complexGeometry.getContent(); 333 } else { 334 throw new MissingParameterValueException( getClass().getName(), 335 "The required Input Parameter InputGeometry is missing." ); 336 } 337 338 // check for optional values 339 if ( null != inputs.get( APPROXIMATION_QUANTIZATION ) ) { 340 IOValue ioApproxQuant = inputs.get( APPROXIMATION_QUANTIZATION ); 341 TypedLiteral literalApproxQuant = ioApproxQuant.getLiteralValue(); 342 this.approximationQuantization = Integer.parseInt( literalApproxQuant.getValue() ); 343 } else { 344 // okay, parameter is optional. Default value will be assigned. 345 } 346 if ( null != inputs.get( CAP_STYLE ) ) { 347 IOValue ioCapStyle = inputs.get( CAP_STYLE ); 348 TypedLiteral literalCapStyle = ioCapStyle.getLiteralValue(); 349 this.capStyle = Integer.parseInt( literalCapStyle.getValue() ); 350 } else { 351 // okay, parameter is optional. Default value will be assigned. 352 } 353 } 354 355 /** 356 * Read configured data inputs for validation. 357 * 358 * @param configuredDataInputs 359 */ 360 private void readSupportedInputs( ProcessDescription.DataInputs configuredDataInputs ) { 361 // Get list of configured/supported/mandatory??? WPSInputDescriptions 362 // from configuredDataInputs 363 List<InputDescription> inputDescriptions = configuredDataInputs.getInputDescriptions(); 364 365 // Get inputDescription for each configured input 366 Iterator<InputDescription> inputDescriptionIterator = inputDescriptions.iterator(); 367 // TODO write variables for each input separately 368 while ( inputDescriptionIterator.hasNext() ) { 369 // Read values from inputDescription 370 InputDescription inputDescription = inputDescriptionIterator.next(); 371 this._abstract = inputDescription.getAbstract(); 372 this.identifier = inputDescription.getIdentifier(); 373 this.title = inputDescription.getTitle(); 374 } 375 } 376 377 /** 378 * Method for validating provided input parameters against configured input parameters. 379 * Actually, this is a very sophisticated implementation. 380 * 381 * @return 382 */ 383 private boolean validate() { 384 boolean isValid = true; 385 386 return isValid; 387 } 388 389 private ProcessOutputs process() 390 throws OGCWebServiceException { 391 ProcessOutputs processOutputs = new ExecuteResponse.ProcessOutputs(); 392 // Create ProcessOutputs DataStructure 393 Object content = bufferContent(); 394 this.complexValue = new ComplexValue( this.format, this.encoding, this.schema, content ); 395 IOValue ioValue = new IOValue( this.identifier, this._abstract, this.title, null, this.complexValue, null, 396 this.literalValue ); 397 List<IOValue> processOutputsList = new ArrayList<IOValue>( 1 ); 398 processOutputsList.add( ioValue ); 399 processOutputs.setOutputs( processOutputsList ); 400 return processOutputs; 401 } 402 403 /** 404 * 405 * @return 406 * @throws OGCWebServiceException 407 */ 408 private Feature bufferContent() 409 throws OGCWebServiceException { 410 org.deegree.model.spatialschema.Geometry buffered = null; 411 Feature result = null; 412 413 // determine if Geometry is Feature collection 414 if ( content instanceof FeatureCollection && content instanceof Feature ) { 415 // if content is a FeatureCollection, cast explicitly to 416 // FeatureCollection 417 FeatureCollection featureCollection = (FeatureCollection) this.content; 418 // split FeatureCollection into an array of features 419 Feature[] features = featureCollection.toArray(); 420 int size = features.length; 421 // preinitialize a FeatureCollection for the buffered features 422 FeatureCollection resultFeatureCollection = FeatureFactory.createFeatureCollection( "BufferedFeatures", 423 size ); 424 Feature f = null; 425 426 // iterate over every feature of the array and perform buffer 427 // operation. afterwards store result into feature collection 428 for ( int i = 0; i < size; i++ ) { 429 f = features[i]; 430 buffered = bufferGeometry( f, this.bufferDistance, this.capStyle, this.approximationQuantization ); 431 // convert from Geometry to Feature 432 resultFeatureCollection.add( convertToFeature( buffered ) ); 433 // set result value 434 result = resultFeatureCollection; 435 } 436 } 437 438 // determine if Geometry is Feature 439 if ( content instanceof Feature && !( content instanceof FeatureCollection ) ) { 440 // if content is a Feature, cast explicitly to Feature 441 Feature feature = (Feature) content; 442 buffered = bufferGeometry( feature, this.bufferDistance, this.capStyle, this.approximationQuantization ); 443 // convert from Geometry to Feature 444 result = convertToFeature( buffered ); 445 } 446 // return result. In case result is a FeatureCollection, an (result 447 // instanceof FeatureCollection) will return true. 448 return result; 449 450 } 451 452 /** 453 * This methods implements the actual buffer process. 454 * 455 * 456 * @param f 457 * Feature to buffer 458 * @param bufferDistance 459 * @param capStyle 460 * @param approximationQuantization 461 * @return 462 * @throws OGCWebServiceException 463 */ 464 private org.deegree.model.spatialschema.Geometry bufferGeometry( Feature feature, int bufferDistance, int capStyle, 465 int approximationQuantization ) 466 throws OGCWebServiceException { 467 // Read the geometry property values from the provided feature 468 org.deegree.model.spatialschema.Geometry[] geomArray = feature.getGeometryPropertyValues(); 469 // initialize (null) Geometry (JTS) for the output of BufferProcess 470 Geometry buffered = null; 471 // initialize (null) Geometry (deegree) 472 org.deegree.model.spatialschema.Geometry bufferedDeegreeGeometry = null; 473 // BufferOp allow optional values for capStyle and 474 // approximationQuantization (JTS) 475 BufferOp options = null; 476 477 // iterate over Geometries 478 for ( int j = 0; j < geomArray.length; j++ ) { 479 try { 480 // convert Geometries to JTS Geometries for buffer 481 Geometry unbuffered = JTSAdapter.export( geomArray[j] ); 482 // set buffer options and get the result geometry 483 options = new BufferOp( unbuffered ); 484 options.setEndCapStyle( capStyle ); 485 options.setQuadrantSegments( approximationQuantization ); 486 buffered = options.getResultGeometry( bufferDistance ); 487 // convert back to Geometry (deegree) 488 bufferedDeegreeGeometry = JTSAdapter.wrap( buffered ); 489 LOG.logInfo( "Buffered Geometry with a distance of " + bufferDistance + " , a capStyle of " + capStyle 490 + " , and an approximation quantization of " + approximationQuantization + "." ); 491 } catch ( GeometryException e ) { 492 LOG.logError( e.getMessage() ); 493 throw new OGCWebServiceException( "Something went wrong while processing buffer operation." ); 494 } 495 } 496 return bufferedDeegreeGeometry; 497 } 498 499 /** 500 * 501 * Convert a Geometry (deegree) to a Feature 502 * 503 * @param bufferedGeometry 504 * @return 505 * 506 */ 507 private Feature convertToFeature( org.deegree.model.spatialschema.Geometry bufferedGeometry ) { 508 PropertyType[] propertyTypeArray = new PropertyType[1]; 509 propertyTypeArray[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, 510 false ); 511 // FIXME set EPSG 512 FeatureType ft = FeatureFactory.createFeatureType( "Buffer", false, propertyTypeArray ); 513 FeatureProperty[] featurePropertyArray = new FeatureProperty[1]; 514 featurePropertyArray[0] = FeatureFactory.createFeatureProperty( new QualifiedName( "GEOM" ), bufferedGeometry ); 515 Feature feature = FeatureFactory.createFeature( "id", ft, featurePropertyArray ); 516 517 return feature; 518 } 519 }