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    }