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    }