001 //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_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 }