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