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