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