7.6. Web Feature Service using Docker

The 3DCityDB Web Feature Service (WFS) Docker image exposes the capabilities of the Web Feature Service for dockerized applications and workflows. Using the WFS Docker you can expose the features stored in a 3DCityDB instance through an OGC WFS interface offering a rich set of features like advanced filter capabilities. For a basic configuration just the connection credentials of the 3DCityDB (CITYDB_* environment variables) have to be specified. All WFS functionalities are supported by the images.

Synopsis

docker run --name wfs [-d] -p 8080:8080 \
    [-e CITYDB_TYPE=postgresql|oracle] \
    [-e CITYDB_HOST=the.host.de] \
    [-e CITYDB_PORT=thePort] \
    [-e CITYDB_NAME=theDBName] \
    [-e CITYDB_SCHEMA=theCityDBSchemaName] \
    [-e CITYDB_USERNAME=theUsername] \
    [-e CITYDB_PASSWORD=theSecretPass] \
    [-e WFS_CONTEXT_PATH=wfs-context-path] \
    [-e WFS_ADE_EXTENSIONS_PATH=/path/to/ade-extensions/] \
    [-e WFS_CONFIG_FILE=/path/to/config.xml] \
    [-v /my/data/config.xml:/path/to/config.xml] \
  3dcitydb/wfs[:TAG]

When running containers with default settings, the WFS will listen at following URL. Note that the web root is used as context path in this case.

http[s]://[host][:port]/wfs

The Web-based client is available here:

http[s]://[host][:port]/wfsclient

7.6.1. Image variants and versions

The WFS Docker images are based on the official Apache Tomcat images and are available as Debian and Alpine Linux variants. Table 7.29 gives an overview on the images available. Currently, Tomcat 9 images are used as base images for the WFS.

The edge images are automatically built and published on every push to the master branch of the 3DCityDB WFS Github repository using the latest stable version of the base images. The latest and release image versions are only built when a new release is published on Github. The latest tag will point to the most recent release version.

Table 7.29 3DCityDB WFS Docker image variants and versions
Tag Debian Alpine
edge deb-build-edge deb-size-edge alp-build-edge alp-size-edge
latest deb-size-latest alp-size-latest
5.0.0 deb-size-v5.0.0 alp-size-v5.0.0

The images are available on 3DCityDB DockerHub and can be pulled like this:

docker pull 3dcitydb/wfs:TAG

The image tag is composed of the WFS version and the image variant. Debian is the default image variant, where no image variant is appended to the tag. For the Alpine Linux images -alpine is appended. The full list of available tags can be found on DockerHub. Here are some examples of full image tags:

docker pull 3dcitydb/wfs:edge
docker pull 3dcitydb/wfs:edge-alpine
docker pull 3dcitydb/wfs:latest-alpine
docker pull 3dcitydb/wfs:5.0.0
docker pull 3dcitydb/wfs:5.0.0-alpine

7.6.2. Usage and configuration

A 3DCityDB WFS Docker container is configured using environment variables and a WFS config.xml file. The easiest way of using the WFS Docker is to use the default config.xml shipped inside the image and by setting the database connection details and/or the web context path through environment variables. The default config file exposes all filter capabilities and feature types from the connected database to the WFS and should be suitable for most situations.

If you require more specific settings, get a copy of default-config.xml and build your own config file (see Configuring the WFS). Mount your custom config file to the container at runtime (see docker run docs). To apply the custom config file set the WFS_CONFIG_FILE option.

All available environment variables are listed and described below.

Note

The environment variables are optional. If you do not provide them, make sure that your config.xml file contains all settings (including database connection details) required to run the service. Otherwise, the WFS will throw error messages when starting the container. If you use environment variables though, they always take precedence over corresponding settings in the config.xml file. Thus, you can create custom config files and use them with different databases by overwriting the settings with the environment variables.

CITYDB_TYPE=<postgresql|oracle>

The type of the 3DCityDB to connect to. postgresql is the default.

CITYDB_HOST=<hostname or ip>

Name of the host or IP address on which the 3DCityDB is running.

CITYDB_PORT=<port>

Port of the 3DCityDB to connect to. Default is 5432 for PostgreSQL and 1521 for Oracle, depending on the setting of CITYDB_TYPE.

CITYDB_NAME=<dbName>

Name of the 3DCityDB database to connect to.

CITYDB_SCHEMA=<citydb>

Schema to use when connecting to the 3DCityDB. The defaults are citydb for PostgreSQL, username for Oracle, depending on the setting of CITYDB_TYPE.

CITYDB_USERNAME=<username>

Username to use when connecting to the 3DCityDB

CITYDB_PASSWORD=<thePassword>

Password to use when connecting to the 3DCityDB

WFS_CONFIG_FILE=</path/to/custom/config.xml>

Path of the WFS config file to use. See above how to create and use a custom config file.

WFS_CONTEXT_PATH=<wfs-context-path>

The URL subpath where the WFS is served (see Section 7.4.1.2). The default value is ROOT, for serving from the web root. Note: Nested paths are currently not supported. For instance, set WFS_CONTEXT_PATH=citydb-wfs to serve from http[s]://my-domain/citydb-wfs/.

WFS_ADE_EXTENSIONS_PATH=</path/to/ade-extension/>

Allows for providing an alternative directory where the WFS service shall search for ADE extensions (default: ade-extensions folder is the WEB-INF directory). The WFS service must have read access to this directory (see Section 7.3 for more details).

7.6.3. Build your own images

3DCityDB WFS images can easily be built on your own. The images support the following build arguments:

BUILDER_IMAGE_TAG=<11.0.12-jdk-slim'>

Tag of the builder base image, https://hub.docker.com/_/openjdk.

RUNTIME_IMAGE_TAG=<9-alpine>

Tag of the runtime image, https://hub.docker.com/_/tomcat.

DEFAULT_CONFIG=</path/to/default/config.xml>

Name of the default config file shall that shall be copied into the image and used by default when running a container. The config file must be located inside the resources/docker folder (default: default-config.xml).

TOMCAT_USER=<tomcat>

Name of the user running the Tomcat service inside the container (default: tomcat). Note that the user is assigned the fixed UID = 1000.

TOMCAT_GROUP=<tomcat>

Name of the group that the user shall be assigned to (default: tomcat). Note that the group is assigned the fixed GID = 1000.

Build process

  1. Clone the WFS Github repository and navigate to the cloned repo:

    git clone https://github.com/3dcitydb/web-feature-service.git
    cd web-feature-service
    
  2. Build the image using docker build:

# Debian variant
docker build . \
  -t 3dcitydb/wfs:edge

# Alpine variant
docker build . \
  -t 3dcitydb/wfs:edge-alpine \
  -f Dockerfile.alpine

7.6.4. Examples

This example shows how to bring up a 3DCityDB WFS with the Importer/Exporter and 3DCityDB Docker images. In this example we are going to provide the LoD3 Railway dataset via WFS and run some example queries.

Database creation and data import

Note

A more detailed example on importing data using the 3DCityDB Docker images is available here.

  1. Download the dataset, create a folder and put the downloaded file in the new folder. In the following we assume the file is at /my/data/Railway_Scene_LoD3.zip.
  2. Create a Docker network and a 3DCityDB Docker container for our dataset:
docker network create citydb-net

docker run -d --name citydb \
  --network citydb-net \
  -e "POSTGRES_PASSWORD=changeMe" \
  -e "SRID=3068" \
3dcitydb/3dcitydb-pg:latest-alpine
  1. Import the dataset using the 3DCityDB Importer/Exporter Docker:
docker run -i -t --rm --name impexp \
    --network citydb-net \
    -v /my/data:/data \
  3dcitydb/impexp:latest-alpine import \
    -H citydb \
    -d postgres \
    -u postgres \
    -p changeMe \
    /data/Railway_Scene_LoD3.zip

WFS configuration and testing

Start a 3DCityDB WFS container. We are going to expose port 8080 to the host system for the service and serve WFS content from /citydb-wfs.

docker run -d --name wfs \
    -p 8080:8080 \
    --network citydb-net \
    -e CITYDB_HOST=citydb \
    -e CITYDB_NAME=postgres \
    -e CITYDB_USERNAME=postgres \
    -e CITYDB_PASSWORD=changeMe \
    -e WFS_CONTEXT_PATH=citydb-wfs \
  3dcitydb/wfs:latest-alpine

Note

The 3DCityDB, Importer/Exporter and WFS Docker containers are attached to the same Docker network citydb-net we created in the beginning. Thus, container names (e.g. citydb) can be use as hostnames for communication between the containers. See Docker network docs for more Docker networking options.

Now the WFS should be up and running. Let’s check if the service started using docker logs:

$ docker logs -n 5 wfs

03-Sep-2021 12:24:14.036 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [/usr/local/tomcat/webapps/host-manager]
03-Sep-2021 12:24:14.049 INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [/usr/local/tomcat/webapps/host-manager] has finished in [13] ms
03-Sep-2021 12:24:14.052 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["http-nio-8080"]
03-Sep-2021 12:24:14.058 INFO [main] org.apache.coyote.AbstractProtocol.start Starting ProtocolHandler ["ajp-nio-8009"]
03-Sep-2021 12:24:14.061 INFO [main] org.apache.catalina.startup.Catalina.start Server startup in 515 ms

If you see output similar to this, the service started successfully.

Get WFS capabilities

The service is listening on port 8080 on our local machine, the Web-based client can be accessed from a browser:

  • WFS service endpoint: http://localhost:8080/citydb-wfs/wfs
  • WFS Web-based client: http://localhost:8080/citydb-wfs/wfsclient

Let’s query the capabilities document to check what our WFS can do. We are going to use curl for this:

serviceURL='http://localhost:8080/citydb-wfs/wfs?'
query='SERVICE=WFS&REQUEST=GetCapabilities'
curl -v "$serviceURL$query"

The capabilities document returned looks like this:

<?xml version="1.0" standalone="yes"?>
<wfs:WFS_Capabilities xmlns:fes="http://www.opengis.net/fes/2.0" xmlns:gml="http://www.opengis.net/gml" xmlns:wtr="http://www.opengis.net/citygml/waterbody/2.0" xmlns:ows="http://www.opengis.net/ows/1.1" xmlns:veg="http://www.opengis.net/citygml/vegetation/2.0" xmlns:tran="http://www.opengis.net/citygml/transportation/2.0" xmlns:dem="http://www.opengis.net/citygml/relief/2.0" xmlns:grp="http://www.opengis.net/citygml/cityobjectgroup/2.0" xmlns:bldg="http://www.opengis.net/citygml/building/2.0" xmlns:wfs="http://www.opengis.net/wfs/2.0" xmlns:tun="http://www.opengis.net/citygml/tunnel/2.0" xmlns:frn="http://www.opengis.net/citygml/cityfurniture/2.0" xmlns:gen="http://www.opengis.net/citygml/generics/2.0" xmlns:brid="http://www.opengis.net/citygml/bridge/2.0" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:luse="http://www.opengis.net/citygml/landuse/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.opengis.net/wfs/2.0 http://schemas.opengis.net/wfs/2.0/wfs.xsd" version="2.0.0">
  <ows:ServiceIdentification>
    <ows:Title>3DCityDB Web Feature Service</ows:Title>
    <ows:ServiceType>WFS</ows:ServiceType>
    <ows:ServiceTypeVersion>2.0.0</ows:ServiceTypeVersion>
  </ows:ServiceIdentification>
  <ows:ServiceProvider>
    <ows:ProviderName/>
    <ows:ServiceContact/>
  </ows:ServiceProvider>
  <ows:OperationsMetadata>
    <ows:Operation name="GetCapabilities">
      <ows:DCP>
        <ows:HTTP>
          <ows:Get xlink:href="http://localhost:8080/citydb-wfs/wfs"/>
          <ows:Post xlink:href="http://localhost:8080/citydb-wfs/wfs"/>

<!-- ... -->
<!-- ... -->

      </fes:SpatialOperators>
    </fes:Spatial_Capabilities>
  </fes:Filter_Capabilities>
</wfs:WFS_Capabilities

Example query: Feature by ID

Now let’s query a feature by ID (GMLID_BUI46739_1739_10911) from the WFS.

The WFS request for this looks like this and is stored in request.xml:

Listing 7.1 request.xml
<?xml version="1.0" encoding="UTF-8"?>
<wfs:GetFeature service="WFS" version="2.0.0" xmlns:wfs="http://www.opengis.net/wfs/2.0">
  <wfs:StoredQuery id="http://www.opengis.net/def/query/OGC-WFS/0/GetFeatureById">
    <wfs:Parameter name="id">GMLID_BUI46739_1739_10911</wfs:Parameter>
  </wfs:StoredQuery>
</wfs:GetFeature>

Let’s send a POST request with the content from request.xml to the WFS and and write the output to building.gml:

curl -v \
  -X POST \
  -H 'Content-Type: text/xml' \
  -d "@request.xml" \
  "http://localhost:8080/citydb-wfs/wfs" > building.gml

The shortened and beautified content of building.gml looks like this:

<?xml version="1.0" standalone="yes"?>
<bldg:Building gml:id="GMLID_BUI46739_1739_10911">
  <gml:description>Simple Chapel with a recess/loggia</gml:description>
  <gml:name>Chapel KIT/KHH-1</gml:name>
  <gml:boundedBy>
    <gml:Envelope srsName="urn:ogc:def:crs:EPSG::3068" srsDimension="3">
      <gml:lowerCorner>-299.374655062533 575.1129259060015 103.648365247638</gml:lowerCorner>
      <gml:upperCorner>-272.47917424008 596.1169211194645 121.04746928772363</gml:upperCorner>
    </gml:Envelope>
  </gml:boundedBy>
  <core:creationDate>2021-09-03</core:creationDate>
  <core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
  <bldg:outerBuildingInstallation>
    <bldg:BuildingInstallation gml:id="UUID_071439a3-5cd7-4ace-b0cb-4cedec5a6540">
      <gml:name>Tower</gml:name>
      <core:creationDate>2021-09-03</core:creationDate>
      <core:relativeToTerrain>entirelyAboveTerrain</core:relativeToTerrain>
      <bldg:function>1040</bldg:function>
      <bldg:lod3Geometry>
        <gml:MultiSurface gml:id="UUID_87c65640-96ad-42d2-aa2d-367245f4a865">

<!-- ... -->
<!-- ... -->

Shutdown and cleanup

When the services and network are no longer required, they can be removed:

docker rm citydb wfs
docker network remove citydb-net