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 }