    001    //$HeadURL: https://svn.wald.intevation.org/svn/deegree/base/branches/2.4_testing/src/org/deegree/ogcwebservices/wps/execute/processes/Buffer.java $
    002    /*----------------------------------------------------------------------------
    003     This file is part of deegree, http://deegree.org/
    004     Copyright (C) 2001-2009 by:
    005       Department of Geography, University of Bonn
    006     and
    007       lat/lon GmbH
    008
    009     This library is free software; you can redistribute it and/or modify it under
    010     the terms of the GNU Lesser General Public License as published by the Free
    011     Software Foundation; either version 2.1 of the License, or (at your option)
    012     any later version.
    013     This library is distributed in the hope that it will be useful, but WITHOUT
    014     ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
    015     FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
    016     details.
    017     You should have received a copy of the GNU Lesser General Public License
    018     along with this library; if not, write to the Free Software Foundation, Inc.,
    019     59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
    020
    021     Contact information:
    022
    023     lat/lon GmbH
    024     Aennchenstr. 19, 53177 Bonn
    025     Germany
    026     http://lat-lon.de/
    027
    028     Department of Geography, University of Bonn
    029     Prof. Dr. Klaus Greve
    030     Postfach 1147, 53001 Bonn
    031     Germany
    032     http://www.geographie.uni-bonn.de/deegree/
    033
    034     e-mail: info@deegree.org
    035    ----------------------------------------------------------------------------*/
    036
    037    package org.deegree.ogcwebservices.wps.execute.processes;
    038
    039    import java.net.URI;
    040    import java.net.URL;
    041    import java.util.ArrayList;
    042    import java.util.Iterator;
    043    import java.util.List;
    044    import java.util.Map;
    045
    046    import org.deegree.datatypes.Code;
    047    import org.deegree.datatypes.QualifiedName;
    048    import org.deegree.datatypes.Types;
    049    import org.deegree.datatypes.values.TypedLiteral;
    050    import org.deegree.framework.log.ILogger;
    051    import org.deegree.framework.log.LoggerFactory;
    052    import org.deegree.model.feature.Feature;
    053    import org.deegree.model.feature.FeatureCollection;
    054    import org.deegree.model.feature.FeatureFactory;
    055    import org.deegree.model.feature.FeatureProperty;
    056    import org.deegree.model.feature.schema.FeatureType;
    057    import org.deegree.model.feature.schema.PropertyType;
    058    import org.deegree.model.spatialschema.GeometryException;
    059    import org.deegree.model.spatialschema.JTSAdapter;
    060    import org.deegree.ogcwebservices.MissingParameterValueException;
    061    import org.deegree.ogcwebservices.OGCWebServiceException;
    062    import org.deegree.ogcwebservices.wps.describeprocess.InputDescription;
    063    import org.deegree.ogcwebservices.wps.describeprocess.ProcessDescription;
    064    import org.deegree.ogcwebservices.wps.execute.ComplexValue;
    065    import org.deegree.ogcwebservices.wps.execute.ExecuteResponse;
    066    import org.deegree.ogcwebservices.wps.execute.IOValue;
    067    import org.deegree.ogcwebservices.wps.execute.OutputDefinition;
    068    import org.deegree.ogcwebservices.wps.execute.OutputDefinitions;
    069    import org.deegree.ogcwebservices.wps.execute.Process;
    070    import org.deegree.ogcwebservices.wps.execute.ExecuteResponse.ProcessOutputs;
    071
    072    import com.vividsolutions.jts.geom.Geometry;
    073    import com.vividsolutions.jts.operation.buffer.BufferOp;
    074
    075    /**
    076     * Buffer.java
    077     *
    078     * Created on 09.03.2006. 23:42:39h
    079     *
    080     * This class describes an exemplary Process Implementation. The corresponding configuration document is '<root>\WEB-INF\conf\wps\processConfigs.xml'.
    081     * Process configuration is described further inside the configuration document.
    082     *
    083     * The process implementor has to ensure, that the process implemented extends the abstract super class Process.
    084     *
    085     * This example process IS NOT intended to describe a best practice approach. In some cases simplifying assumptions have
    086     * been made for sake of simplicity.
    087     *
    088     * @author <a href="mailto:christian@kiehle.org">Christian Kiehle</a>
    089     * @author <a href="mailto:christian.heier@gmx.de">Christian Heier</a>
    090     *
    091     * @version 1.0.
    092     *
    093     * @since 2.0
    094     */
    095
    096    public class Buffer extends Process {
    097
    098        static int id = 0;
    099
    100        /**
    101         *
    102         * @param processDescription
    103         */
    104        public Buffer( ProcessDescription processDescription ) {
    105            super( processDescription );
    106        }
    107
    108        // define an ILogger for this class
    109        private static final ILogger LOG = LoggerFactory.getLogger( Buffer.class );
    110
    111        /**
    112         * The provided buffer implementation relies on the Java Topology Suite
    113         * <link>http://www.vividsolutions.com/jts/JTSHome.htm</link>. Buffering is an operation which in GIS is used to
    114         * compute the area containing all points within a given distance of a Geometry. A buffer takes at least two inputs:
    115         *
    116         * 1.) The Geometry to buffer (point, linestring, polygon, etc.) 2.) The distance of the buffer.
    117         *
    118         * The provided Buffer defines two optional elements as well:
    119         *
    120         * 3.) The end cap style, which determines how the linework for the buffer polygon is constructed at the ends of
    121         * linestrings. Possible styles are: a) CAP_ROUND The usual round end caps (integer value of 1) b) CAP_BUTT End caps
    122         * are truncated flat at the line ends (integer value of 2) c) CAP_SQUARE End caps are squared off at the buffer
    123         * distance beyond the line ends (integer value of 2)
    124         *
    125         * 4.) Since the exact buffer outline of a Geometry usually contains circular sections, the buffer must be
    126         * approximated by the linear Geometry. The degree of approximation may be controlled by the user. This is done by
    127         * specifying the number of quadrant segments used to approximate a quarter-circle. Specifying a larger number of
    128         * segments results in a better approximation to the actual area, but also results in a larger number of line
    129         * segments in the computed polygon. The default value is 8.
    130         *
    131         */
    132
    133        /*******************************************************************************************************************
    134         * <wps:DataInputs>
    135         ******************************************************************************************************************/
    136
    137        /**
    138         * The input section defines four elements: 1) BufferDistance (mandatory), wich will be mapped to
    139         *
    140         * <code>private int bufferDistance</code> 2) CapStyle (optional, when ommited, default value 1 will be used),
    141         * which will be mapped to <code>private int capStyle</code> 3) ApproximationQuantization (optional, when ommited,
    142         * default value 8 will be used), which will be mapped to <code>private int approximationQuantization</code> 4)
    143         * InputGeometry (mandatory), which will be mapped to <code>private Object content</code>
    144         *
    145         * To illustrate the use, the first and fourth input Elements are included below.
    146         *
    147         */
    148
    149        // "BufferDistance", "CapStyle", and "ApproximationQuantization" refer to
    150        // the corresponding <ows:Identifier/> elements.
    151        private static final String BUFFER_DISTANCE = "BufferDistance";
    152
    153        private static final String CAP_STYLE = "CapStyle";
    154
    155        private static final String APPROXIMATION_QUANTIZATION = "ApproximationQuantization";
    156
    157        private static final String INPUT_GEOMETRY = "InputGeometry";
    158
    159        /*******************************************************************************************************************
    160         * <wps:Input> <ows:Identifier>BufferDistance</ows:Identifier> <ows:Title>BufferDistance</ows:Title>
    161         * <ows:Abstract>Width of Buffer</ows:Abstract> <wps:LiteralValue dataType="urn:ogc:def:dataType:OGC:0.0:Double"
    162         * uom="urn:ogc:def:dataType:OGC:1.0:metre"> 50</wps:LiteralValue> </wps:Input>
    163         ******************************************************************************************************************/
    164
    165        // the required attributes for calculating a spatial buffer, initialized
    166        // with default-values.
    167        private int bufferDistance = 0;
    168
    169        private int capStyle = 1;
    170
    171        private int approximationQuantization = 8;
    172
    173        /**
    174         * the content represents the <wps:ComplexValue/> Element in the ProcessInput section. This sample process is feeded
    175         * with a feature collection, resulting in <wps:ComplexValue format="text/xml" encoding="UTF-8"
    176         * schema="http://schemas.opengis.net/gml/3.0.0/base/gml.xsd"> <wfs:FeatureCollection
    177         * xmlns:gml="http://www.opengis.net/gml" xmlns:wfs="http://www.opengis.net/wfs"
    178         * xmlns:app="http://www.deegree.org/app" xmlns:xlink="http://www.w3.org/1999/xlink"> <gml:boundedBy> <gml:Envelope>
    179         * <gml:pos>2581829.334 5660821.982</gml:pos> <gml:pos>2582051.078 5661086.442</gml:pos> </gml:Envelope>
    180         * </gml:boundedBy> <gml:featureMember> <app:flurstuecke gml:id="ID_10208"> <app:gid></app:gid> <app:id></app:id>
    181         * <app:rechtswert>2581969.20000000020</app:rechtswert> <app:hochwert>5660957.50000000000</app:hochwert>
    182         * <app:datum></app:datum> <app:folie></app:folie> <app:objart></app:objart> <app:aliasfolie>Flurstuecke</app:aliasfolie>
    183         * <app:aliasart>Flurstueck</app:aliasart> <app:alknr></app:alknr> <app:gemarkung></app:gemarkung> <app:flur></app:flur>
    184         * <app:zaehler></app:zaehler> <app:nenner></app:nenner> <app:beschrift></app:beschrift> <app:the_geom>
    185         * <gml:MultiPolygon srsName="EPSG:31466"> <gml:polygonMember> <gml:Polygon srsName="EPSG:31466">
    186         * <gml:outerBoundaryIs> <gml:LinearRing> <gml:coordinates cs="," decimal="." ts=" ">2581856.436,5660874.757
    187         * 2581947.164,5660938.093 2581940.797,5660952.002 2581936.158,5660962.135 2581971.597,5660982.717
    188         * 2581971.83,5660982.852 2581969.62,5660994.184 2581967.616,5661004.464 2581959.465,5661016.584
    189         * 2581958.555,5661017.679 2581967.415,5661024.833 2581974.177,5661032.529 2582021.543,5661086.442
    190         * 2582051.078,5661001.919 2582002.624,5660957.782 2581960.501,5660919.412 2581956.98,5660916.972
    191         * 2581904.676,5660880.734 2581878.263,5660853.196 2581868.096,5660842.595 2581848.325,5660821.982
    192         * 2581829.334,5660840.172 2581837.725,5660850.881 2581856.436,5660874.757</gml:coordinates> </gml:LinearRing>
    193         * </gml:outerBoundaryIs> </gml:Polygon> </gml:polygonMember> </gml:MultiPolygon> </app:the_geom> </app:flurstuecke>
    194         * </gml:featureMember> </wfs:FeatureCollection> </wps:ComplexValue>
    195         *
    196         */
    197
    198        private Object content = null;
    199
    200        // Values for ProcessOutput, will be filled dynamically.
    201
    202        private Code identifier = null;
    203
    204        private String title = null;
    205
    206        private String _abstract = null;
    207
    208        private URL schema = null;
    209
    210        @SuppressWarnings("unused")
    211        private URI uom = null;
    212
    213        private String format = null;
    214
    215        private URI encoding = null;
    216
    217        private ComplexValue complexValue = null;
    218
    219        private TypedLiteral literalValue = null;
    220
    221        /**
    222         * (non-Javadoc)
    223         *
    224         * @see org.deegree.ogcwebservices.wps.execute.Process
    225         *
    226         * This is the central method for implementing a process. A <code>Map<String,IOValue></code> serves as an input
    227         * object. Each String represents the key (e.g. BufferDistance) which holds an IOValue as value (e.g. an object
    228         * representing a complete <wps:Input> element with all corresponding sub-elements). The process implementation is
    229         * responsible for retrieving all specified values according to the process configuration document.
    230         *
    231         * The method returns a <code>ProcessOutputs</code> object, which encapsulates the result of the process's
    232         * operation.
    233         *
    234         */
    235        @Override
    236        public ProcessOutputs execute( Map<String, IOValue> inputs, OutputDefinitions outputDefinitions )
    237                                throws OGCWebServiceException {
    238
    239            LOG.logDebug( "execute Buffer invoked." );
    240            // delegate the read out of parameters to a private method
    241            readValuesFromInputDefinedValues( inputs );
    242
    243            // get configured ( = supported) inputs from processDescription
    244            ProcessDescription.DataInputs configuredDataInputs = processDescription.getDataInputs();
    245
    246            // delegate the read out of configured ( = supported ) inputs to a
    247            // private method
    248            readSupportedInputs( configuredDataInputs );
    249
    250            // delegate the read out of configured outputs to a private method
    251            readOutputDefinitions( outputDefinitions );
    252
    253            // Define a processOutputs object
    254            // validate, that data inputs correspond to process descritption
    255            ProcessOutputs processOutputs = null;
    256            boolean isValid = validate();
    257            if ( isValid ) {
    258                processOutputs = process();
    259
    260            } else {
    261                LOG.logError( "Data input is invalid." );
    262                throw new OGCWebServiceException( "Buffer", "The configuration is invalid" );
    263            }
    264
    265            return processOutputs;
    266        }
    267
    268        /**
    269         * FIXME Assumes (simplified for the actual process) that only one output is defined. Reads the output definitions
    270         * into local variables.
    271         *
    272         * @param outputDefinitions
    273         */
    274        private void readOutputDefinitions( OutputDefinitions outputDefinitions ) {
    275            List<OutputDefinition> outputDefinitionList = outputDefinitions.getOutputDefinitions();
    276            Iterator<OutputDefinition> outputDefinitionListIterator = outputDefinitionList.iterator();
    277            while ( outputDefinitionListIterator.hasNext() ) {
    278                OutputDefinition outputDefinition = outputDefinitionListIterator.next();
    279                this._abstract = outputDefinition.getAbstract();
    280                this.title = outputDefinition.getTitle();
    281                this.identifier = outputDefinition.getIdentifier();
    282                this.schema = outputDefinition.getSchema();
    283                this.format = outputDefinition.getFormat();
    284                this.encoding = outputDefinition.getEncoding();
    285                this.uom = outputDefinition.getUom();
    286            }
    287        }
    288
    289        /**
    290         * Private method for assigning input values to local variables
    291         *
    292         * @param inputs
    293         * @throws OGCWebServiceException
    294         */
    295        private void readValuesFromInputDefinedValues( Map<String, IOValue> inputs )
    296                                throws OGCWebServiceException {
    297
    298            // check for mandatory values
    299            if ( null != inputs.get( BUFFER_DISTANCE ) ) {
    300                IOValue ioBufferDistance = inputs.get( BUFFER_DISTANCE );
    301                TypedLiteral literalBuffer = ioBufferDistance.getLiteralValue();
    302                this.bufferDistance = Integer.parseInt( literalBuffer.getValue() );
    303            } else {
    304                throw new MissingParameterValueException( getClass().getName(),
    305                                                          "The required Input Parameter BufferDistance is missing." );
    306            }
    307
    308            if ( null != inputs.get( INPUT_GEOMETRY ) ) {
    309                IOValue ioGeometry = inputs.get( INPUT_GEOMETRY );
    310                ComplexValue complexGeometry = ioGeometry.getComplexValue();
    311                this.content = complexGeometry.getContent();
    312            } else {
    313                throw new MissingParameterValueException( getClass().getName(),
    314                                                          "The required Input Parameter InputGeometry is missing." );
    315            }
    316
    317            // check for optional values
    318            if ( null != inputs.get( APPROXIMATION_QUANTIZATION ) ) {
    319                IOValue ioApproxQuant = inputs.get( APPROXIMATION_QUANTIZATION );
    320                TypedLiteral literalApproxQuant = ioApproxQuant.getLiteralValue();
    321                this.approximationQuantization = Integer.parseInt( literalApproxQuant.getValue() );
    322            } else {
    323                // okay, parameter is optional. Default value will be assigned.
    324            }
    325            if ( null != inputs.get( CAP_STYLE ) ) {
    326                IOValue ioCapStyle = inputs.get( CAP_STYLE );
    327                TypedLiteral literalCapStyle = ioCapStyle.getLiteralValue();
    328                this.capStyle = Integer.parseInt( literalCapStyle.getValue() );
    329            } else {
    330                // okay, parameter is optional. Default value will be assigned.
    331            }
    332        }
    333
    334        /**
    335         * Read configured data inputs for validation.
    336         *
    337         * @param configuredDataInputs
    338         */
    339        private void readSupportedInputs( ProcessDescription.DataInputs configuredDataInputs ) {
    340            // Get list of configured/supported/mandatory??? WPSInputDescriptions
    341            // from configuredDataInputs
    342            List<InputDescription> inputDescriptions = configuredDataInputs.getInputDescriptions();
    343
    344            // Get inputDescription for each configured input
    345            Iterator<InputDescription> inputDescriptionIterator = inputDescriptions.iterator();
    346            // TODO write variables for each input separately
    347            while ( inputDescriptionIterator.hasNext() ) {
    348                // Read values from inputDescription
    349                InputDescription inputDescription = inputDescriptionIterator.next();
    350                this._abstract = inputDescription.getAbstract();
    351                this.identifier = inputDescription.getIdentifier();
    352                this.title = inputDescription.getTitle();
    353            }
    354        }
    355
    356        /**
    357         * Method for validating provided input parameters against configured input parameters. <b> Not implemented right
    358         * now! </b>
    359         *
    360         * @return true
    361         */
    362        private boolean validate() {
    363            boolean isValid = true;
    364
    365            return isValid;
    366        }
    367
    368        private ProcessOutputs process()
    369                                throws OGCWebServiceException {
    370            ProcessOutputs processOutputs = new ExecuteResponse.ProcessOutputs();
    371            // Create ProcessOutputs DataStructure
    372            Object content = bufferContent();
    373            this.complexValue = new ComplexValue( this.format, this.encoding, this.schema, content );
    374            IOValue ioValue = new IOValue( this.identifier, this._abstract, this.title, null, this.complexValue, null,
    375                                           this.literalValue );
    376            List<IOValue> processOutputsList = new ArrayList<IOValue>( 1 );
    377            processOutputsList.add( ioValue );
    378            processOutputs.setOutputs( processOutputsList );
    379            return processOutputs;
    380        }
    381
    382        /**
    383         *
    384         * @return buffered result. In case result is a FeatureCollection, (result instanceof FeatureCollection) will return
    385         *         true.
    386         * @throws OGCWebServiceException
    387         */
    388        private Feature bufferContent()
    389                                throws OGCWebServiceException {
    390            org.deegree.model.spatialschema.Geometry[] buffered = null;
    391            Feature result = null;
    392
    393            // determine if Geometry is Feature collection
    394            if ( content instanceof FeatureCollection && content instanceof Feature ) {
    395                // if content is a FeatureCollection, cast explicitly to
    396                // FeatureCollection
    397                FeatureCollection featureCollection = (FeatureCollection) this.content;
    398                // split FeatureCollection into an array of features
    399                Feature[] features = featureCollection.toArray();
    400                int size = features.length;
    401                // preinitialize a FeatureCollection for the buffered features
    402                FeatureCollection resultFeatureCollection = FeatureFactory.createFeatureCollection( "BufferedFeatures",
    403                                                                                                    size );
    404                Feature f = null;
    405
    406                // iterate over every feature of the array and perform buffer
    407                // operation. afterwards store result into feature collection
    408                for ( int i = 0; i < size; i++ ) {
    409                    f = features[i];
    410                    buffered = bufferGeometry( f, this.bufferDistance, this.capStyle, this.approximationQuantization );
    411
    412                    // generate QualifiedName for buffered Feature from original Feature
    413                    QualifiedName oldQN = ( f.getFeatureType().getProperties() )[0].getName();
    414                    QualifiedName newQN = new QualifiedName( oldQN.getPrefix(), "Buffer", oldQN.getNamespace() );
    415
    416                    // convert from Geometry to Feature
    417                    for ( int j = 0; j < buffered.length; j++ ) {
    418                        resultFeatureCollection.add( convertToFeature( buffered[j], id, newQN ) );
    419                        id++;
    420                    }
    421                    // set result value
    422                    result = resultFeatureCollection;
    423                }
    424            }
    425
    426            // determine if Geometry is Feature
    427            if ( content instanceof Feature && !( content instanceof FeatureCollection ) ) {
    428                // if content is a Feature, cast explicitly to Feature
    429                Feature feature = (Feature) content;
    430                buffered = bufferGeometry( feature, this.bufferDistance, this.capStyle, this.approximationQuantization );
    431
    432                // generate QualifiedName for buffered Feature from original Feature
    433                QualifiedName oldQN = ( feature.getFeatureType().getProperties() )[0].getName();
    434                QualifiedName newQN = new QualifiedName( oldQN.getPrefix(), "Buffer", oldQN.getNamespace() );
    435
    436                // convert from Geometry to Feature
    437                result = convertToFeature( buffered[0], id, newQN );
    438                id++;
    439            }
    440            // return result. In case result is a FeatureCollection, an (result
    441            // instanceof FeatureCollection) will return true.
    442            return result;
    443
    444        }
    445
    446        /**
    447         * This methods implements the actual buffer process.
    448         *
    449         *
    450         * @param feature
    451         *            Feature to buffer
    452         * @param bufferDistance
    453         * @param capStyle
    454         * @param approximationQuantization
    455         * @return an array of buffered deegree geometries
    456         * @throws OGCWebServiceException
    457         */
    458        private org.deegree.model.spatialschema.Geometry[] bufferGeometry( Feature feature, int bufferDistance,
    459                                                                           int capStyle, int approximationQuantization )
    460                                throws OGCWebServiceException {
    461            // Read the geometry property values from the provided feature
    462            org.deegree.model.spatialschema.Geometry[] geomArray = feature.getGeometryPropertyValues();
    463            if ( geomArray == null || geomArray.length == 0 ) {
    464                throw new OGCWebServiceException( "No geometries found." );
    465            }
    466
    467            // initialize (null) Geometry (JTS) for the output of BufferProcess
    468            Geometry buffered = null;
    469            // initialize (null) Geometry (deegree)
    470            org.deegree.model.spatialschema.Geometry[] bufferedDeegreeGeometry = new org.deegree.model.spatialschema.Geometry[geomArray.length];
    471            // BufferOp allow optional values for capStyle and
    472            // approximationQuantization (JTS)
    473            BufferOp options = null;
    474
    475            // iterate over Geometries
    476            for ( int j = 0; j < geomArray.length; j++ ) {
    477                try {
    478                    // convert Geometries to JTS Geometries for buffer
    479                    Geometry unbuffered = JTSAdapter.export( geomArray[j] );
    480                    // set buffer options and get the result geometry
    481                    options = new BufferOp( unbuffered );
    482                    options.setEndCapStyle( capStyle );
    483                    options.setQuadrantSegments( approximationQuantization );
    484                    buffered = options.getResultGeometry( bufferDistance );
    485                    // convert back to Geometry (deegree)
    486                    bufferedDeegreeGeometry[j] = JTSAdapter.wrap( buffered );
    487                    LOG.logInfo( "Buffered Geometry with a distance of " + bufferDistance + " , a capStyle of " + capStyle
    488                                 + " , and an approximation quantization of " + approximationQuantization + "." );
    489                } catch ( GeometryException e ) {
    490                    LOG.logError( e.getMessage() );
    491                    throw new OGCWebServiceException( "Something went wrong while processing buffer operation." );
    492                }
    493            }
    494            return bufferedDeegreeGeometry;
    495        }
    496
    497        /**
    498         *
    499         * Convert a Geometry (deegree) to a Feature
    500         *
    501         * @param bufferedGeometry
    502         * @return a Feature representation of Input bufferedGeometry
    503         *
    504         */
    505        private Feature convertToFeature( org.deegree.model.spatialschema.Geometry bufferedGeometry, int id,
    506                                          QualifiedName oQN ) {
    507            PropertyType[] propertyTypeArray = new PropertyType[1];
    508            propertyTypeArray[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( oQN.getPrefix(), "GEOM",
    509                                                                                               oQN.getNamespace() ),
    510                                                                            Types.GEOMETRY, false );
    511            // FIXME set EPSG
    512            FeatureType ft = FeatureFactory.createFeatureType( oQN, false, propertyTypeArray );
    513            FeatureProperty[] featurePropertyArray = new FeatureProperty[1];
    514            featurePropertyArray[0] = FeatureFactory.createFeatureProperty( new QualifiedName( oQN.getPrefix(), "GEOM",
    515                                                                                               oQN.getNamespace() ),
    516                                                                            bufferedGeometry );
    517            Feature feature = FeatureFactory.createFeature( "ID_" + id, ft, featurePropertyArray );
    518
    519            return feature;
    520        }
    521    }