deegree WFS (Web Feature Service)
This page provides an overview on the deegree Web Feature Service.
Contents
1. Introduction
"The deegree Web Feature Service provides customisable access to vector data and supports complex schema mappings."
Er... but what does this mean, exactly?
A WFS serves "feature types". If you look at it from an XML schema point-of-view, feature types are XML elements that are substitutable for the abstract "gml:_Feature" element. The "gml:_Feature" element is defined in the GML core schemas, along with dozens of other elements and types, such as geometries. As the possibilities of XML schemas as well as the GML core schemas are fairly complex, not every possible GML application schema can be served by current WFS implementations. However, the deegree WFS is quite sophisticated in respect to the possibilities it offers for defining feature types. Here are some highlights:
- supports properties that contain features themselves (complex features)
- multi-properties (properties that may occur several times in a feature)
- recursive feature/property structures
- "dynamic" content, e.g. content of properties may be generated using SQL functions
- XSL-scripts can be used to preprocess incoming requests and postprocess outgoing responses
2. Implemented OGC standards
- WFS implementation specification 1.1.0
WFS implementation specification 1.0.0 (by incorporating XSL-processing): see WebFeatureService100Compatibility
Filter Encoding Implementation Specification 1.1.0, 1.0.0: http://www.opengeospatial.org/standards/filter
3. Known issues
The deegree WFS is (of course) not perfect. Some of the issues are implementation related and can be resolved, others stem from fundamental problems in the implementation specification.
4. Supported request types / encodings
The WFS Implementation Specification defines XML as well as KVP (key-value-pair) encodings for different request types. However, the KVP encoded requests only support a subset of the possibilities of their XML counterparts. Therefore, XML is the primary encoding for WFS requests and not all KVP requests are currently implemented by the deegree WFS.
4.1. Non-Transactional
Operation |
KVP |
XML |
GetCapabilities |
YES |
YES |
DescribeFeatureType |
YES |
YES |
GetFeature |
YES* |
YES |
GetGmlObject |
NO |
NO |
* except BBOX-Parameter, because name of GeometryProperty must not be specified (use encoded Filter with BBOX as workaround)
4.2. Transactional
Operation |
KVP |
XML |
Transaction |
NO |
YES |
LockFeature |
NO |
YES |
5. Configuration
TransactionalWebFeatureServiceConfiguration - Configuring transactional WFS
ArcSDEBackend - Configuring deegree WFS against ArcSDE
6. How to write a custom Datastore implementation
If you need full (Java) control over the processing of a Query (part of a GetFeature request), you can simply write your own Datastore implementation:
Use this as a template (and have a look at org.deegree.io.datastore.shape.ShapeDatastore for a simple Datastore that actually does something):
package org.deegree.io.datastore;
import org.deegree.io.datastore.schema.MappedFeatureType;
import org.deegree.model.feature.FeatureCollection;
import org.deegree.ogcwebservices.wfs.operation.LockFeature;
import org.deegree.ogcwebservices.wfs.operation.Query;
public class CustomDatastore extends Datastore {
/**
* Performs a query against the datastore.
*
* @param query
* query to be performed
* @param rootFt
* the root feature type that is queried
* @return requested feature instances
* @throws DatastoreException
*/
@Override
public FeatureCollection performQuery( Query query, MappedFeatureType rootFt )
throws DatastoreException {
// for a read-only WFS, you need to implement this method only
return null;
}
@Override
public FeatureCollection performQuery( Query query, MappedFeatureType rootFt, DatastoreTransaction context )
throws DatastoreException {
return performQuery( query, rootFt );
}
@Override
public FeatureCollection performQueryWithLock( Query query )
throws DatastoreException {
// TODO Auto-generated method stub
return null;
}
@Override
public void performLockFeature( LockFeature request )
throws DatastoreException {
// TODO Auto-generated method stub
}
@Override
public void close()
throws DatastoreException {
// TODO Auto-generated method stub
}
@Override
public DatastoreTransaction acquireTransaction()
throws DatastoreException {
// TODO Auto-generated method stub
return null;
}
@Override
public void releaseTransaction( DatastoreTransaction ta )
throws DatastoreException {
// TODO Auto-generated method stub
}
}
Next thing that you have to do:
Add a mapping to org.deegree.io.datastore.datastores.properties:
... ORACLE=org.deegree.io.datastore.sql.oracle.OracleDatastore POSTGIS=org.deegree.io.datastore.sql.postgis.PostGISDatastore SHAPE=org.deegree.io.datastore.shape.ShapeDatastore GENERICSQL=org.deegree.io.datastore.sql.generic.GenericSQLDatastore SDE=org.deegree.io.datastore.sde.SDEDatastore CUSTOM=org.deegree.io.datastore.CUSTOMDatastore
Afterwards, the WFS will know the new backend 'CUSTOM', and the Datastore implementation will be used to process queries, if the mapped application schema requests it.
<?xml version="1.0" encoding="UTF-8"?>
<schema ...>
<import .../>
<import .../>
<!-- configuration of the persistence backend to be used -->
<annotation>
<appinfo>
<deegreewfs:Prefix>...</deegreewfs:Prefix>
<!-- CUSTOM (CUSTOMDatastore) ->
<deegreewfs:Backend>CUSTOM</deegreewfs:Backend>
<deegreewfs:DefaultSRS>...</deegreewfs:DefaultSRS>
<deegreewfs:SuppressXLinkOutput>...</deegreewfs:SuppressXLinkOutput>
$
If you require read access only, this is all you need to do.
7. How a GetFeature-query is performed internally (on SQL based datastores)
Consider an incoming GetFeature request:
<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature version="1.1.0" xmlns:app="http://www.deegree.org/app" xmlns:wfs="http://www.opengis.net/wfs" xmlns:gml="http://www.opengis.net/gml" xmlns:ogc="http://www.opengis.net/ogc" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs
http://schemas.opengis.net/wfs/1.1.0/wfs.xsd">
<wfs:Query typeName="app:Philosopher">
<ogc:Filter>
<ogc:PropertyIsEqualTo>
<ogc:PropertyName>app:placeOfBirth/app:Place/app:country/app:Country/app:name</ogc:PropertyName>
<ogc:Literal>France</ogc:Literal>
</ogc:PropertyIsEqualTo>
</ogc:Filter>
</wfs:Query>
</wfs:GetFeature>
For each wfs:Query-element, a 2 phase-request is performed (SQL-wise) to build the result:
- An *initial* SQL-SELECT statement is issued to determines the ids of all features that match the specified ogc:Filter criterion. To improve efficiency, all necessary properties from the "root-feature" table (the queried feature type's main table; in this case: Philosopher) are fetched by the initial statement as well.
- For complex feature types (as Philosopher), the graph of connected tables is traversed in order to fetch the data for all properties and subfeatures.
7.1. Phase 1: Initial SELECT
SELECT X1.PLACE_OF_BIRTH,X1.NAME,X1.DATE_OF_BIRTH,X1.SEX,X1.DATE_OF_DEATH,X1.ID,X1.PLACE_OF_DEATH FROM PHILOSOPHER X1 LEFT OUTER JOIN PLACE X2 ON X2.ID=X1.PLACE_OF_BIRTH LEFT OUTER JOIN COUNTRY X3 ON X3.ID=X2.COUNTRY_ID WHERE X3.NAME = 'France'
As you can see, only columns from the "root" table (PHILOSOPHER) are fetched initially:
- X1.PLACE_OF_BIRTH
- X1.NAME
- X1.DATE_OF_BIRTH
- X1.SEX
- X1.DATE_OF_DEATH
- X1.ID
- X1.PLACE_OF_DEATH
Also, the statement builds a chain of LEFT OUTER JOINs in order to constrain the result to these rows in the PHILOSOPHER table which match the filter expression (informally: app:placeOfBirth/app:Place/app:country/app:Country/app:name='France').
InitialSelectStrategy - Why does the WFS always use LEFT OUTER JOINS? INNER JOINS can be much faster!
7.2. Phase 2: Follow-up SELECTs
TODO