/*---------------- FILE HEADER ------------------------------------------ This file is part of deegree. Copyright (C) 2001-2005 by: EXSE, Department of Geography, University of Bonn http://www.giub.uni-bonn.de/exse/ lat/lon GmbH http://www.lat-lon.de This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Contact: Andreas Poth lat/lon GmbH Aennchenstraße 19 53177 Bonn Germany E-Mail: poth@lat-lon.de Prof. Dr. Klaus Greve Department of Geography University of Bonn Meckenheimer Allee 166 53115 Bonn Germany E-Mail: greve@giub.uni-bonn.de ---------------------------------------------------------------------------*/ package org.deegree.ogcwebservices.wps.execute.processes; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Map; import org.deegree.datatypes.Code; import org.deegree.datatypes.QualifiedName; import org.deegree.datatypes.Types; import org.deegree.datatypes.values.TypedLiteral; import org.deegree.framework.log.ILogger; import org.deegree.framework.log.LoggerFactory; import org.deegree.model.feature.Feature; import org.deegree.model.feature.FeatureCollection; import org.deegree.model.feature.FeatureFactory; import org.deegree.model.feature.FeatureProperty; import org.deegree.model.feature.schema.FeatureType; import org.deegree.model.feature.schema.PropertyType; import org.deegree.model.spatialschema.GeometryException; import org.deegree.model.spatialschema.JTSAdapter; import org.deegree.ogcwebservices.MissingParameterValueException; import org.deegree.ogcwebservices.OGCWebServiceException; import org.deegree.ogcwebservices.wps.describeprocess.InputDescription; import org.deegree.ogcwebservices.wps.describeprocess.ProcessDescription; import org.deegree.ogcwebservices.wps.execute.ComplexValue; import org.deegree.ogcwebservices.wps.execute.ExecuteResponse; import org.deegree.ogcwebservices.wps.execute.IOValue; import org.deegree.ogcwebservices.wps.execute.OutputDefinition; import org.deegree.ogcwebservices.wps.execute.OutputDefinitions; import org.deegree.ogcwebservices.wps.execute.Process; import org.deegree.ogcwebservices.wps.execute.ExecuteResponse.ProcessOutputs; import com.vividsolutions.jts.geom.Geometry; import com.vividsolutions.jts.operation.buffer.BufferOp; /** * Buffer.java * * Created on 09.03.2006. 23:42:39h * * This class describes an exemplary Process Implementation. The corresponding * configuration document is '<root>\WEB-INF\conf\wps\processConfigs.xml'. * Process configuration is described further inside the configuration document. * * The process implementor has to ensure, that the process implemented extends * the abstract super class Process. * * This example process IS NOT intended to describe a best practice approach. In * some cases simplifying assumptions have been made for sake of simplicity. * * @author <a href="mailto:christian@kiehle.org">Christian Kiehle</a> * @author <a href="mailto:christian.heier@gmx.de">Christian Heier</a> * * @version 1.0. * * @since 2.0 */ public class Buffer extends Process { /** * * @param processDescription */ public Buffer( ProcessDescription processDescription ) { super( processDescription ); } // define an ILogger for this class private static final ILogger LOG = LoggerFactory.getLogger( Buffer.class ); /** * The provided buffer implementation relies on the Java Topology Suite * <link>http://www.vividsolutions.com/jts/JTSHome.htm</link>. Buffering is * an operation which in GIS is used to compute the area containing all * points within a given distance of a Geometry. A buffer takes at least two * inputs: * * 1.) The Geometry to buffer (point, linestring, polygon, etc.) 2.) The * distance of the buffer. * * The provided Buffer defines two optional elements as well: * * 3.) The end cap style, which determines how the linework for the buffer * polygon is constructed at the ends of linestrings. Possible styles are: * a) CAP_ROUND The usual round end caps (integer value of 1) b) CAP_BUTT * End caps are truncated flat at the line ends (integer value of 2) c) * CAP_SQUARE End caps are squared off at the buffer distance beyond the * line ends (integer value of 2) * * 4.) Since the exact buffer outline of a Geometry usually contains * circular sections, the buffer must be approximated by the linear * Geometry. The degree of approximation may be controlled by the user. This * is done by specifying the number of quadrant segments used to approximate * a quarter-circle. Specifying a larger number of segments results in a * better approximation to the actual area, but also results in a larger * number of line segments in the computed polygon. The default value is 8. * */ /*************************************************************************** * <wps:DataInputs> **************************************************************************/ /** * The input section defines four elements: 1) BufferDistance (mandatory), * wich will be mapped to * * @see <code>private int bufferDistance</code> 2) CapStyle (optional, * when ommited, default value 1 will be used), which will be mapped to * @see <code>private int capStyle</code> 3) ApproximationQuantization * (optional, when ommited, default value 8 will be used), which will * be mapped to * @see <code>private int approximationQuantization</code> 4) * InputGeometry (mandatory), which will be mapped to * @see <code>private Object content</code> * * To illustrate the use, the first and fourth input Elements are included * below. * */ // "BufferDistance", "CapStyle", and "ApproximationQuantization" refer to // the corresponding <ows:Identifier/> elements. private static final String BUFFER_DISTANCE = "BufferDistance"; private static final String CAP_STYLE = "CapStyle"; private static final String APPROXIMATION_QUANTIZATION = "ApproximationQuantization"; private static final String INPUT_GEOMETRY = "InputGeometry"; /*************************************************************************** * <wps:Input> <ows:Identifier>BufferDistance</ows:Identifier> * <ows:Title>BufferDistance</ows:Title> <ows:Abstract>Width of Buffer</ows:Abstract> * <wps:LiteralValue dataType="urn:ogc:def:dataType:OGC:0.0:Double" * uom="urn:ogc:def:dataType:OGC:1.0:metre"> 50</wps:LiteralValue> * </wps:Input> **************************************************************************/ // the required attributes for calculating a spatial buffer, initialized // with default-values. private int bufferDistance = 0; private int capStyle = 1; private int approximationQuantization = 8; /** * the content represents the <wps:ComplexValue/> Element in the * ProcessInput section. This sample process is feeded with a feature * collection, resulting in <wps:ComplexValue format="text/xml" * encoding="UTF-8" * schema="http://schemas.opengis.net/gml/3.0.0/base/gml.xsd"> * <wfs:FeatureCollection xmlns:gml="http://www.opengis.net/gml" * xmlns:wfs="http://www.opengis.net/wfs" * xmlns:app="http://www.deegree.org/app" * xmlns:xlink="http://www.w3.org/1999/xlink"> <gml:boundedBy> * <gml:Envelope> <gml:pos>2581829.334 5660821.982</gml:pos> * <gml:pos>2582051.078 5661086.442</gml:pos> </gml:Envelope> * </gml:boundedBy> <gml:featureMember> <app:flurstuecke gml:id="ID_10208"> * <app:gid></app:gid> <app:id></app:id> * <app:rechtswert>2581969.20000000020</app:rechtswert> * <app:hochwert>5660957.50000000000</app:hochwert> <app:datum></app:datum> * <app:folie></app:folie> <app:objart></app:objart> * <app:aliasfolie>Flurstuecke</app:aliasfolie> <app:aliasart>Flurstueck</app:aliasart> * <app:alknr></app:alknr> <app:gemarkung></app:gemarkung> <app:flur></app:flur> * <app:zaehler></app:zaehler> <app:nenner></app:nenner> <app:beschrift></app:beschrift> * <app:the_geom> <gml:MultiPolygon srsName="EPSG:31466"> * <gml:polygonMember> <gml:Polygon srsName="EPSG:31466"> * <gml:outerBoundaryIs> <gml:LinearRing> <gml:coordinates cs="," * decimal="." ts=" ">2581856.436,5660874.757 2581947.164,5660938.093 * 2581940.797,5660952.002 2581936.158,5660962.135 2581971.597,5660982.717 * 2581971.83,5660982.852 2581969.62,5660994.184 2581967.616,5661004.464 * 2581959.465,5661016.584 2581958.555,5661017.679 2581967.415,5661024.833 * 2581974.177,5661032.529 2582021.543,5661086.442 2582051.078,5661001.919 * 2582002.624,5660957.782 2581960.501,5660919.412 2581956.98,5660916.972 * 2581904.676,5660880.734 2581878.263,5660853.196 2581868.096,5660842.595 * 2581848.325,5660821.982 2581829.334,5660840.172 2581837.725,5660850.881 * 2581856.436,5660874.757</gml:coordinates> </gml:LinearRing> * </gml:outerBoundaryIs> </gml:Polygon> </gml:polygonMember> * </gml:MultiPolygon> </app:the_geom> </app:flurstuecke> * </gml:featureMember> </wfs:FeatureCollection> </wps:ComplexValue> * */ private Object content = null; // Values for ProcessOutput, will be filled dynamically. private Code identifier = null; private String title = null; private String _abstract = null; private URL schema = null; @SuppressWarnings ( "unused") private URI uom = null; private String format = null; private URI encoding = null; private ComplexValue complexValue = null; private TypedLiteral literalValue = null; /** * (non-Javadoc) * * @see org.deegree.ogcwebservices.wps.execute.Process#execute(org.deegree.ogcwebservices.wps.execute.ExecuteRequest) * * This is the central method for implementing a process. A * <code>Map<String,IOValue></code> serves as an input object. Each * String represents the key (e.g. BufferDistance) which holds an IOValue as * value (e.g. an object representing a complete <wps:Input> element with * all corresponding sub-elements). The process implementation is * responsible for retrieving all specified values according to the process * configuration document. * * The method returns a <code>ProcessOutputs</code> object, which * encapsulates the result of the process's operation. * */ public ProcessOutputs execute( Map<String, IOValue> inputs, OutputDefinitions outputDefinitions ) throws OGCWebServiceException { LOG.entering(); // delegate the read out of parameters to a private method readValuesFromInputDefinedValues( inputs ); // get configured ( = supported) inputs from processDescription ProcessDescription.DataInputs configuredDataInputs = processDescription.getDataInputs(); // delegate the read out of configured ( = supported ) inputs to a // private method readSupportedInputs( configuredDataInputs ); // delegate the read out of configured outputs to a private method readOutputDefinitions( outputDefinitions ); // Define a processOutputs object // validate, that data inputs correspond to process descritption ProcessOutputs processOutputs = null; boolean isValid = validate(); if ( isValid ) { processOutputs = process(); } else { LOG.logError( "Data input is invalid." ); throw new OGCWebServiceException( "Buffer", "The configuraiton is invalid" ); } LOG.exiting(); return processOutputs; } /** * FIXME Assumes (simplified for the actual process) that only one output is * defined. Reads the output definitions into local variables. * * @param outputDefinitions */ private void readOutputDefinitions( OutputDefinitions outputDefinitions ) { List<OutputDefinition> outputDefinitionList = outputDefinitions.getOutputDefinitions(); Iterator<OutputDefinition> outputDefinitionListIterator = outputDefinitionList.iterator(); while ( outputDefinitionListIterator.hasNext() ) { OutputDefinition outputDefinition = outputDefinitionListIterator.next(); this._abstract = outputDefinition.getAbstract(); this.title = outputDefinition.getTitle(); this.identifier = outputDefinition.getIdentifier(); this.schema = outputDefinition.getSchema(); this.format = outputDefinition.getFormat(); this.encoding = outputDefinition.getEncoding(); this.uom = outputDefinition.getUom(); } } /** * Private method for assigning input values to local variables * * @param inputs * @throws OGCWebServiceException */ private void readValuesFromInputDefinedValues( Map<String, IOValue> inputs ) throws OGCWebServiceException { // check for mandatory values if ( null != inputs.get( BUFFER_DISTANCE ) ) { IOValue ioBufferDistance = inputs.get( BUFFER_DISTANCE ); TypedLiteral literalBuffer = ioBufferDistance.getLiteralValue(); this.bufferDistance = Integer.parseInt( literalBuffer.getValue() ); } else { throw new MissingParameterValueException( getClass().getName(), "The required Input Parameter BufferDistance is missing." ); } if ( null != inputs.get( INPUT_GEOMETRY ) ) { IOValue ioGeometry = inputs.get( INPUT_GEOMETRY ); ComplexValue complexGeometry = ioGeometry.getComplexValue(); this.content = complexGeometry.getContent(); } else { throw new MissingParameterValueException( getClass().getName(), "The required Input Parameter InputGeometry is missing." ); } // check for optional values if ( null != inputs.get( APPROXIMATION_QUANTIZATION ) ) { IOValue ioApproxQuant = inputs.get( APPROXIMATION_QUANTIZATION ); TypedLiteral literalApproxQuant = ioApproxQuant.getLiteralValue(); this.approximationQuantization = Integer.parseInt( literalApproxQuant.getValue() ); } else { // okay, parameter is optional. Default value will be assigned. } if ( null != inputs.get( CAP_STYLE ) ) { IOValue ioCapStyle = inputs.get( CAP_STYLE ); TypedLiteral literalCapStyle = ioCapStyle.getLiteralValue(); this.capStyle = Integer.parseInt( literalCapStyle.getValue() ); } else { // okay, parameter is optional. Default value will be assigned. } } /** * Read configured data inputs for validation. * * @param configuredDataInputs */ private void readSupportedInputs( ProcessDescription.DataInputs configuredDataInputs ) { // Get list of configured/supported/mandatory??? WPSInputDescriptions // from configuredDataInputs List<InputDescription> inputDescriptions = configuredDataInputs.getInputDescriptions(); // Get inputDescription for each configured input Iterator<InputDescription> inputDescriptionIterator = inputDescriptions.iterator(); // TODO write variables for each input separately while ( inputDescriptionIterator.hasNext() ) { // Read values from inputDescription InputDescription inputDescription = inputDescriptionIterator.next(); this._abstract = inputDescription.getAbstract(); this.identifier = inputDescription.getIdentifier(); this.title = inputDescription.getTitle(); } } /** * Method for validating provided input parameters against configured input * parameters. Actually, this is a very sophisticated implementation. * * @return */ private boolean validate() { boolean isValid = true; return isValid; } private ProcessOutputs process() throws OGCWebServiceException { ProcessOutputs processOutputs = new ExecuteResponse.ProcessOutputs(); // Create ProcessOutputs DataStructure Object content = bufferContent(); this.complexValue = new ComplexValue( this.format, this.encoding, this.schema, content ); IOValue ioValue = new IOValue( this.identifier, this._abstract, this.title, null, this.complexValue, null, this.literalValue ); List<IOValue> processOutputsList = new ArrayList<IOValue>( 1 ); processOutputsList.add( ioValue ); processOutputs.setOutputs( processOutputsList ); return processOutputs; } /** * * @return * @throws OGCWebServiceException */ private Feature bufferContent() throws OGCWebServiceException { org.deegree.model.spatialschema.Geometry buffered = null; Feature result = null; // determine if Geometry is Feature collection if ( content instanceof FeatureCollection && content instanceof Feature ) { // if content is a FeatureCollection, cast explicitly to // FeatureCollection FeatureCollection featureCollection = ( FeatureCollection ) this.content; // split FeatureCollection into an array of features Feature[] features = featureCollection.toArray(); int size = features.length; // preinitialize a FeatureCollection for the buffered features FeatureCollection resultFeatureCollection = FeatureFactory.createFeatureCollection( "BufferedFeatures", size ); Feature f = null; // iterate over every feature of the array and perform buffer // operation. afterwards store result into feature collection for ( int i = 0; i < size; i++ ) { f = features[i]; buffered = bufferGeometry( f, this.bufferDistance, this.capStyle, this.approximationQuantization ); // convert from Geometry to Feature resultFeatureCollection.add( convertToFeature( buffered ) ); // set result value result = resultFeatureCollection; } } // determine if Geometry is Feature if ( content instanceof Feature && !( content instanceof FeatureCollection ) ) { // if content is a Feature, cast explicitly to Feature Feature feature = ( Feature ) content; buffered = bufferGeometry( feature, this.bufferDistance, this.capStyle, this.approximationQuantization ); // convert from Geometry to Feature result = convertToFeature( buffered ); } // return result. In case result is a FeatureCollection, an (result // instanceof FeatureCollection) will return true. return result; } /** * This methods implements the actual buffer process. * * * @param f * Feature to buffer * @param bufferDistance * @param capStyle * @param approximationQuantization * @return * @throws OGCWebServiceException */ private org.deegree.model.spatialschema.Geometry bufferGeometry( Feature feature, int bufferDistance, int capStyle, int approximationQuantization ) throws OGCWebServiceException { // Read the geometry property values from the provided feature org.deegree.model.spatialschema.Geometry[] geomArray = feature.getGeometryPropertyValues(); // initialize (null) Geometry (JTS) for the output of BufferProcess Geometry buffered = null; // initialize (null) Geometry (deegree) org.deegree.model.spatialschema.Geometry bufferedDeegreeGeometry = null; // BufferOp allow optional values for capStyle and // approximationQuantization (JTS) BufferOp options = null; // iterate over Geometries for ( int j = 0; j < geomArray.length; j++ ) { try { // convert Geometries to JTS Geometries for buffer Geometry unbuffered = JTSAdapter.export( geomArray[j] ); // set buffer options and get the result geometry options = new BufferOp( unbuffered ); options.setEndCapStyle( capStyle ); options.setQuadrantSegments( approximationQuantization ); buffered = options.getResultGeometry( bufferDistance ); // convert back to Geometry (deegree) bufferedDeegreeGeometry = JTSAdapter.wrap( buffered ); LOG.logInfo( "Buffered Geometry with a distance of " + bufferDistance + " , a capStyle of " + capStyle + " , and an approximation quantization of " + approximationQuantization + "." ); } catch ( GeometryException e ) { LOG.logError( e.getMessage() ); throw new OGCWebServiceException( "Something went wrong while processing buffer operation." ); } } return bufferedDeegreeGeometry; } /** * * Convert a Geometry (deegree) to a Feature * * @param bufferedGeometry * @return * */ private Feature convertToFeature( org.deegree.model.spatialschema.Geometry bufferedGeometry ) { PropertyType[] propertyTypeArray = new PropertyType[1]; propertyTypeArray[0] = FeatureFactory.createSimplePropertyType( new QualifiedName( "GEOM" ), Types.GEOMETRY, false ); // FIXME set EPSG FeatureType ft = FeatureFactory.createFeatureType( "Buffer", false, propertyTypeArray ); FeatureProperty[] featurePropertyArray = new FeatureProperty[1]; featurePropertyArray[0] = FeatureFactory.createFeatureProperty( "GEOM", bufferedGeometry ); Feature feature = FeatureFactory.createFeature( "id", ft, featurePropertyArray ); return feature; } } /* ******************************************************************** Changes to this class. What the people have been up to: $Log: Buffer.java,v $ Revision 1.3 2006/08/24 06:42:17 poth File header corrected Revision 1.2 2006/05/25 14:47:26 poth LiteralValue substituted by TypedLiteral ********************************************************************** */