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