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 }