qwc-services による Web GIS の構築 #1 qwc-docker

「QGIS データを Web で表示」という一連の記事において、QGIS で作成したデータ（"プロジェクト"）を Web ページで表示する方法について、ノウハウを共有しました。

- [QGIS データを Web で表示 #0 - 目次](https://qiita.com/softark/items/0615ed7611e9a410ab3b) > 
    - [QGIS Server + QGIS Web Client 2 + MapCache](https://qiita.com/softark/items/0615ed7611e9a410ab3b#qgis-server--qgis-web-client-2--mapcache)

そこでは、`QGIS Server`、`QWC2(QGIS Web Client 2)`、`MapCache` をそれぞれ単独でインストールして連携させるという方法を採用しました。

今回は、`QGIS Server` と `QWC2` を中核とし、`QWC2` をサポートする各種サービスを統合した Web GIS 構築環境である `qwc-services` を利用します。

これによって、単体としての `QWC2` では実現できない次のような機能を追加することが出来ます。

- ユーザの認証と権限
- 編集機能
- 全文検索
- コンパクトな永続リンク
- 標高プロファイル
- カスタム地物情報テンプレート
- 地図情報ポップアップ
- レポート (Jasper)

私がもっとも必要としているのは「ユーザの認証と権限」の機能です。これが無いと、ウェブ上で公開できる地図の内容に大きな制約を伴うことになります。つまり、誰に見せても良い地図しか取り扱えないということになると、ウェブ地図アプリの有用性が大きく損なわれることになります。

ですので、当面は、これまでに `QGIS Server` + `QWC2` + `MapCacche` で実現した機能に「ユーザの認証と権限」の機能を追加することを目標にします。

なお、引き続いて AlmaLinux 9.5 の環境で作業を進めます。また、`QWC2` スタンドアロン環境での設定方法がそのまま通用するところは、説明を割愛します。

## 1. Docker

`qwc-services` は一連の `Docker` イメージとして提供されています。

従って、最初に `Docker` をインストールするところから始めます。

普通に `dnf install docker` とすると、なるほど、`Docker` はインストールされるのですが、うまく動いてくれません。`podman-docker` というパッケージが入るのですが、それでは具合が悪いようです。

まず `Docker` の公式レポジトリを追加します。

```bash
sudo dnf config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
```

入っているかも知れない `podman`, `buildah` パッケージを削除。

```bash
sudo dnf remove podman buildah
```

`docker-ce`, `docker-ce-cli`, `containerd.io` をインストール。

```bash
sudo dnf install docker-ce docker-ce-cli containerd.io
```

そして、`docker` サービスを起動。

```bash
sudo systemctl start docker.service
sudo systemctl enable docker.service
```

`docker` がインストールされたかどうかを確認。

```bash
sudo docker version
または
sudo docker info
```

通常ユーザで `docker` 操作ができるように、ユーザ `docker` を 通常ユーザのグループに追加。

```bash
sudo usermod -aG docker $USER
```

グループの追加を有効にするために再起動。

```bash
sudo reboot
```

以上については、下記を参照しました。

- https://linuxconfig.org/install-docker-on-almalinux

## 2. qwc-docker でデモ版をインストール

マニュアルに従って作業を進めます。

- [QWC2 / QWC Services](https://qwc-services.github.io/master/)
    - [Quick start](https://qwc-services.github.io/master/QuickStart/) > Running QWC2 as part of qwc-services

`qwc-docker` レポジトリを `gitHub` からクローンします。

```bash
git clone --recursive https://github.com/qwc-services/qwc-docker.git
```

`qwc-docker` ディレクトリに入って、サンプルの設定ファイルをコピーします。

```bash
cd qwc-docker
cp docker-compose-example.yml docker-compose.yml
cp api-gateway/nginx-example.conf api-gateway/nginx.conf
```

`docker-compose.yml` で `postgres` のスーパユーザのパスワードを設定します。

```yaml:docker-compose.yml
  qwc-postgis:
    image: sourcepole/qwc-base-db:<version>
    environment:
      POSTGRES_PASSWORD: '<SET YOUR PASSWORD HERE>'
```

秘密鍵を作成。

```bash
python3 -c 'import secrets; print("JWT_SECRET_KEY=\"%s\"" % secrets.token_hex(48))' >.env
```

`docker-compose.yml` で QWC サービスを走らせる UID/GID を変更し、ホストの共有ボリュームの所有者に合せます。

```diff_yaml:docker-compose.yml
    x-qwc-service-variables: &qwc-service-variables
      JWT_SECRET_KEY: $JWT_SECRET_KEY
      JWT_COOKIE_CSRF_PROTECT: 'True'
      JWT_COOKIE_SAMESITE: 'Strict'
      # Uncomment if the application is exclusively run over HTTPS to ensure cookies are only sent with HTTPS requests
      # JWT_COOKIE_SECURE: 'True'
      # Set these to override the UID/GID which runs uwsgi. Useful to ensure uwsgi runs under the same user/group
      # which owns the shared volumes on host, avoiding the need to change the ownership/permissions of those files/folders.
-     # SERVICE_UID: 1000
-     # SERVICE_GID: 1000
+     SERVICE_UID: 1000
+     SERVICE_GID: 1000
      # Uncomment this for multi-tenant support, see also api-gateway/nginx-example-multitenant.conf
      # TENANT_HEADER: Tenant
```

上記の `1000` という `UID/GID` は一例です。私の vmware 環境では `1000` でしたが、運用サーバの環境では `1001` でした。`/etc/passwd` を見て調べてください。

共有される `solr` の `data volume` のパーミッションを変更します。

```bash
sudo chown 8983:8983 volumes/solr/data
```

マニュアルには記載されていませんが、`docker compose up` が `version` について不平を述べますので、`docker-compose.yml` で `version` の記述をコメントアウトします。

```diff_yaml:docker-compose.yml
-   version: '3.4'
+   # version: '3.4'
    
    x-qwc-service-variables: &qwc-service-variables
      JWT_SECRET_KEY: $JWT_SECRET_KEY
      JWT_COOKIE_CSRF_PROTECT: 'True'
      JWT_COOKIE_SAMESITE: 'Strict'
      ...
```

全てのコンテナを立ち上げます。初回は全ての `docker` イメージを `dockerhub` からダウンロードするため、少し時間がかかります。

```bash
docker compose up
```

`http://localhost:8088/` にアクセスして、動作を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/7017e5f6-c00e-4d3c-a913-a53e56e853ad.png)

管理画面は `http://localhost:8088/qwc_admin` です。ユーザ名 `admin` パスワード `admin` でログインしてください。初回のログイン時に、パスワードの変更を求められます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/0dcc4ff7-dfb5-42a1-af2e-0fb9ab7dc51a.png)

以上で、デモ版のインストールは完了しました。

ここまでの所は、あっけないほど簡単ですね。

## 3. テーマの構成

### 3-1. テーマの構成ファイル

テーマの構成に関わるファイルは、`qwc-docker/volumes/config-in/<tenant>/themesConfig.json` です。

ここで `<tenant>` は、マルチ・テナントをサポートする場合のテナント名ですが、シングル・テナントの場合は、テナント名が `default` と決っていますので、`qwc-docker/volumes/config-in/default/themesConfig.json` となります。（以下、シングル・テナントを前提として話を進めます）

### 3-2. テーマの自動構成

使用したいテーマ（QGIS プロジェクト）を `qwc-docker/volumes/qgs-resources/scan` ディレクトリの下に配置します。

そして、`QWC Admin` の画面で、`Generate service configuration` を押すと、`themesConfig.json` の設定内容と QGIS Server から取得した QGIS プロジェクトの内容を統合したテーマ設定が自動作成されます。`QWC2` スタンドアロン環境と同じですね。

背景レイヤや外部レイヤの設定を `themesConfig.json` に書くのも、スタンドアロン環境と同じです。

ただし、生成される構成ファイルは、`qwc-docker/volumes/config/default` の下の `mapViewerConfig.json` というファイルです。これは、スタンドアロン環境でのビューワ設定（`config.json`）とテーマ設定（`themes.json`）を一つに合せたものです。`http://localhost:8088/themes.json` でテーマ設定の内容を確認することが出来ます。

#### 3-2-1. デフォルト・テーマの指定

使用したいテーマ（QGIS プロジェクト）をデフォルト・テーマとしたい場合は、`themesConfig.json` の `defaultTheme` を次のように設定できます。

```json:themesConfig.json
    "defaultTheme": "isg",
```

上のように、拡張子 `".qgs"` は付けません。

### 3-3. QGIS プロジェクトの格納場所を変更できる？

マニュアルによると、`themesConfig.json` で `qgis_projects_scan_base_dir` を設定すると、QGIS プロジェクトの格納場所を変更することが可能であるそうですが、手許の環境でいろいろと試してみましたが、`qwc-docker/volumes/qgs-rerouces/scan` ディレクトリ以外をスキャンしてくれませんでした。

マニュアルの誤記で、`themesConfig.json` ではなく、`tenantConfig.json` での設定ではないかと思いますが、試していません。

### 3-4. テーマのサムネール

テーマのサムネール画像は `qwc-docker/volumes/qwc2/assets/img/mapthumbs` の下に `<pboject_basename>.png` （例えば `"isg.png"`）として置きます。

もう一度、テーマの自動構成を実行する必要があります。

### 3-5. 日本語フォント

`qwc-qgis-server` のコンテナには日本語フォントが入っていないので、そのままだと、地物の日本語ラベルが豆腐の列になります。

日本語フォントを入れる必要があるのですが、ホストに入っているフォントを使用することが出来ればそれでオーケーです。

`qwc-docker/docker-compose.yml` の `qwc-qgis-server` で、次のように、ホストの `google-noto-cjk` フォントを `volumes` で共有して読めるようにします。

```diff_yaml::docker-compose.yml
      qwc-qgis-server:
        image: sourcepole/qwc-qgis-server:3.34
        # ports:
        #   - "127.0.0.1:8001:80"
        environment:
          # QGIS_SERVER_LOG_LEVEL: 0
          # LOCALE: 'de_DE'
          # QGIS_SERVER_IGNORE_BAD_LAYERS: 1
          FCGID_EXTRA_ENV: 'PRINT_LAYOUT_DIR'
          PRINT_LAYOUT_DIR: '/layouts'
        volumes:
          - ./volumes/qgs-resources:/data:ro
          - ./pg_service.conf:/etc/postgresql-common/pg_service.conf:ro
          - ./volumes/qgis-server-plugins/print_templates:/usr/share/qgis/python/plugins/print_templates:ro
          - ./volumes/qgis-server-plugins/split_categorized:/usr/share/qgis/python/plugins/split_categorized:ro
          - ./volumes/qgis-server-plugins/filter_geom:/usr/share/qgis/python/plugins/filter_geom:ro
          - ./volumes/qgis-server-plugins/clear_capabilities:/usr/share/qgis/python/plugins/clear_capabilities:ro
          - ./volumes/print-layouts:/layouts:ro
+         - /usr/share/fonts/google-noto-cjk:/usr/share/fonts/google-noto-cjk:ro
```

このように追記してからコンテナを再起動すればオーケーです。

なお、ホストに日本語フォントが入っていない場合もあるようです。その場合は、`dnf` でインストールしてください。

```bash
sudo dnf install google-noto-sans-cjk-jp-fonts google-noto-sans-cjk-ttc-fonts google-noto-serif-cjk-ttc-fonts
```

### 3-6. 背景レイヤの設定

背景レイヤの設定方法は、"QWC2" スタンドアロン環境とほとんど同じです。

`qwc-docker/volumes/config-in/default/themesConfig.json` に下記の内容を追記して、テーマの自動構成を実行します。

```json:themesConfig.json
    ...
    "defaultTheme": "isg",
    "themes": {
        "items": [
            {
                "id": "isg",
                "name": "isg",
                "url": "/ows/scan/isg",
                "backgroundLayers": [
                    {
                        "name": "",
                        "printLayer": "bg"
                    },
                    {
                        "name": "gsijp-std",
                        "printLayer": "gsijp-std"
                    },
                    {
                        "name": "gsijp-photo",
                        "printLayer": "gsijp-photo"
                    },
                    {
                        "name": "g-map",
                        "printLayer": "g-map"
                    },
                    {
                        "name": "g-sat",
                        "printLayer": "g-sat",
                        "visibility": true
                    }
                ]
            },
            ...
        ]
        "backgroundLayers": [
            {
                "name": "gsijp-std",
                "type": "xyz",
                "title": "国土地理院-標準地図",
                "tiled": true,
                "url": "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png",
                "thumbnail": "gsijp-std.png"
            },
            {
                "name": "gsijp-photo",
                "title": "国土地理院-航空写真",
                "type": "xyz",
                "tiled": true,
                "url": "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg",
                "thumbnail": "gsijp-photo.png"
            },
            {
                "name": "g-map",
                "title": "Google Map",
                "type": "xyz",
                "tiled": true,
                "url": "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}",
                "thumbnail": "g-map.png"
            },
            {
                "name": "g-sat",
                "title": "Google Satellite",
                "type": "xyz",
                "tiled": true,
                "url": "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}",
                "thumbnail": "g-sat.png"
            },
            ...
        ]
```

注意点が三つあります。

- テーマの `"url"` を `"/ows/scan/<project>"` の形で必ず指定する
    - `"url"` が正しく指定されていないと、テーマの自動構成でエラーが発生します
    - `"id"` と `"name"` は省略しても構いません
- 背景グループも `"printLayer"` 指定する
    - 上記では `"bg"` が背景グループの識別名です
    - `"printLayer"` 指定をしないと、空っぽのグループだけがレイヤ・ツリーに出現します
    - `"name"` は `""` が良いでしょう
- 背景レイヤのサムネールはテーマのサムネールと同じ場所に置く
    -  `qwc-docker/volumes/qwc2/assets/img/mapthumbs` です

### 3-7. 外部レイヤの設定（MapCache の利用）

外部レイヤを設定して MapCache を利用する方法も、`QWC2` スタンドアロン環境とほとんど同じです。

マニュアルでは QGIS プロジェクトの「外部URL」設定を利用する方法が解説されていますが、`themesConfig.json` を直接に編集する方が確実であり、手間も少ないと思います。

`qwc-docker/volumes/config-in/default/themesConfig.json` に下記の内容を追記して、テーマの自動構成を実行します。

```json:themesConfig.json
    ...
    "defaultTheme": "isg",
    "themes": {
        "items": [
            {
                "id": "isg",
                "name": "isg",
                "url": "/ows/scan/isg",
                "externalLayers": [
                    {
                        "name": "spots",
                        "internalLayer": "spots"
                    }
                ],
                "backgroundLayers": [ ... ],
            },
            ...
        ],
        "externalLayers": [
            {
                "name": "spots",
                "type": "wms",
                "url": "https://webgis.mydomain/mc/",
                "params": {
                    "SERVICE": "WMS",
                    "VERSION": "1.3.0",
                    "TRANSPARENT": true,
                    "FORMAT": "image/png",
                    "LAYERS": "spots",
                    "tiled": true
                },
                "featureInfoUrl": "/api/v1/featureinfo/scan/isg",
                "queryLayers": [
                    "spots"
                ],
                "legendUrl": "/api/v1/legend/scan/isg",
                "infoFormats": [
                    "text/xml"
                ]
            }
        ],
        "backgroundLayers": [ ... ]
```

- `"url"`: 上記は、従来の `QGIS Server + QWC2 + MapCache` の環境で動作している MapCache の `URL` をそのまま使用しています
    - とりあえずは `MapCache` が利用できることを確認できれば良しとしますが、`qwc-services` と `MapCache` を連携させる方法については後で検討します。その結果として、異なる `URL` を使うようになるかもしれません
- `"featureInfoUrl"`:  `qwc-services` 環境では、地物情報の取得を `qwc-feature-info-service` 経由で行いますので、そのための `URL` を指定します
- `"legendurl"`:  `qwc-services` 環境では、凡例の取得を `qwc-legend-service` 経由で行いますので、そのための `URL` を指定します

`MapCache` 側の設定変更も、`/etc/mapcache.xml` における `"source"` の `URL` を `qwc-qgis-server` の `URL` に変更するだけで済みそうです。

### 3-8. その他のテーマ設定

デフォルト縮尺リストや検索プロバイダなどを `QWC2` スタンドアロン環境と同じように設定します。対象の設定ファイルが変るだけで、設定する内容は同じです。

## 4. ビューワの設定

`QWC2` マップ・ビューワの設定方法もスタンドアロン環境とほとんど同じです。

変るのはディレクトリ構成ぐらいです。

- 設定ファイル: 
    - `QWC2` スタンドアロン環境: `qwc2-demo-app/static/config.json`
    - `qwc-docker` 環境: `qwc-docker/volumes/config-in/default/config.json`
- ロゴ用画像ファイル格納ディレクトリ
    - `QWC2` スタンドアロン環境: `qwc2-demo-app/static/assets/img`
    - `qwc-docker` 環境: `qwc-docker/volumes/qwc2/assets/img`

`qwc-services` による各機能の向上や設定内容の追加・変更などがありますが、それについては、おいおいと見ていくことにします。メニュー項目やボタンの追加・削除・変更などであれば、`QWC2` スタンドアロン環境でのノウハウがそのまま通用しますので、ここでは改めての説明は割愛します。

ちなみに、`qwc-docker` のデモ版では、「ポータル」がごっそり削除されています。あれは要りませんよね、やっぱり。

## 5. MapCache との連携

`qwc-services` と `MapCache` を連携させるためには、相互に呼び出しが可能なように、`QGIS Server` と `MapCache` の `URL` を決定することが必要にして十分な条件となります。つまり、各モジュールのルーティングを決めれば良いわけです。

### 5-1. ルーティング

基本構想は以下の通りです。

- `MapCache` はホストで動作しているものをそのまま使用
    - ホストで `MapCache` を動かしている `httpd` もそのまま使用
- `qwc-services` はコンテナで動作しているものをそのまま使用
    - `QGIS Server`
    - `QWC2 Viewer`
    - `qwc-api-gateway` の `nginx` もそのまま使用
- ホストの `nginx` で全体をまとめる
    - `http` ポート(80)、`https` ポート(443) を使う
    - `MapCache` へのルーティング
    - `qwc-services` へのルーティング

```:全体図
-- nginx -----+---- httpd ----------- MapCache
   (80,443)   |     (8080)
              |
　- - - - - - | - - - - - - - - - - - - - - - - - - -
              | 
              +---- nginx ------+---- QGIS Server
                  (8088:80)     +---- QWC2 Viewer
                                +---- etc.,
```

`nginx` が二段のリバース・プロキシを構成してます。

一つにまとめたら良いんじゃないの？とも思いますが、いろいろと面倒くさそうです。

- 前段のホストの `nginx` にまとめた場合
    - `qwc-servises` 内でのルーティングに不安
- 後段のコンテナ内 `nginx` にまとめた場合
    - コンテナ内の `nginx` からホストの `MapCache` へルーティングするという問題
    - Let's Encrypt 証明書更新の問題

結局、前段のホストの `nginx` の設定を変更するだけで済む方法を採るのが簡明で良いと判断しました。

### 5-2. `MapCache` 用 `httpd` 設定

ホストの `MapCache` 用 `httpd` 設定は従来と変りません。

```apache:httpd.conf
...
# listen 80
listen 8080
...
```

```apache:mapcache.conf
<IfModule mapcache_module>
    <Directory "/mc">
        Require all granted
    </Directory>
    MapCacheAlias /mc "/etc/mapcache.xml"
</IfModule>
```

### 5-3. `qwc-api-gateway` コンテナの `nginx` 設定

`docker-compose.yml` の `qwc-api-gateway` にも、`qwc-docker/api-gateway/nginx.conf` にも、変更はありません。

### 5-4. ホストの `nginx` 設定

ホストの `nginx` のための `/etc/nginx/conf.d/webgis.conf` を以下のように編集します。

```nginx:webgis.conf
# webgis.mydomain ... qwc-services / MapCache
server {
    listen 80;
    server_name webgis.mydomain;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    http2 on;
    server_name webgis.mydomain;

    ssl_certificate /etc/pki/tls/certs/vmware.crt;
    ssl_certificate_key /etc/pki/tls/certs/vmware.key;

    # MapCache
    location /mc {
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_pass http://localhost:8080/mc;
    }

    # qwc-services
    location / {
        proxy_set_header    Host                $host;
        proxy_set_header    X-Real-IP           $remote_addr;
        proxy_set_header    X-Forwarded-For     $proxy_add_x_forwarded_for;
        proxy_set_header    X-Forwarded-Proto   $scheme;
        proxy_pass http://localhost:8088;
    }
}
```

要は `MapCache` と `qwc-services` とのルーティングを行っているだけです。

:::note warn
冗長なようですが、`proxy_set_header` は `location` ブロックごとに書く方が安全です。
:::

### 5-5. `MapCache` 外部レイヤの `URL` を変更する

`themesConfig.json` で `MapCache` を参照している外部レイヤの `URL` を全て変更します。

```diff_json::themesConfig.json
            "externalLayers": [
                {
                    "name": "spots",
                    "type": "wms",
-                   "url": "https://webgis.mydomain/mc/",
+                   "url": "/mc",
                    "params": { ... }
                }
```

### 5-6. `MapCache` の `sorce url` を変更する

`/etc/mapcache.xml` で `"source"` として参照している QGIS Server の `URL` を全て変更します。

```diff_xml:mapcache.xml
        <source name="spots" type="wms">
            ...
            <http>
-               <url>https://webgis.mydomain/qsv/isg/</url>
+               <url>https://webgis.mydomain/ows/scan/isg</url>
            </http>
        </source>
```

上記で `https://webgis.mydomain/ows` は、`qwc-services` の中の `qwc-ogc-service` の `URL` です。

:::note warn
これでも通常は動くのですが、レイヤの閲覧権限が制約されている場合に、うまく動作しなくなります。

と言うのは、`qwc-ogc-service` は、`qwc-auth-service` によってユーザの権限をチェックし、閲覧権限がある場合に限って `qwc-qgis-server` を呼び出すからです。すなわち、`MapCache` にしてみると、`public` として全面的に公開されているマップやレイヤでなければ `GetMap` によって地図画像を取得することが出来ないということです。
:::

### 5-7. `MapCache` の `source url` を `qwc-qgis-server` のものに変更する

上記の閲覧権限の問題を回避するために、`MapCache` からは `qwc-ogc-service` を介さずに `qwc-qgis-server` を直接呼び出すことにします。

そのために、まず、`qwc-docker/docker-composer.yml` でコメント・アウトされている `qwc-qgis-server` のポート・マッピングを有効にします。

```diff_yaml:docker-compose.yml
  qwc-qgis-server:
    image: sourcepole/qwc-qgis-server:3.34
-   # ports:
-   #   - "127.0.0.1:8001:80"
+   ports:
+     - "127.0.0.1:8001:80"
    environment:
```

ホスト側のポートは、 `127.0.0.1` すなわち `localhost` を指定していますので、外部からのアクセスは不可能です。

そして、`/etc/mapcache.xml` で `source` の `url` を `qwc-qgis-server` のものに変更します。 

```diff_xml:mapcache.xml
        <source name="spots" type="wms">
            ...
            <http>
-               <url>https://webgis.mydomain/ows/scan/isg</url>
+               <url>http://localhost:8001/ows/scan/isg</url>
            </http>
        </source>
```

`qwc-qgis-server` の `url` はインターネットからはアクセス不可能ですので、セキュリティ上の問題も特にありません。

### 5-8. クッキーのセキュア化

これまでの作業によって Web GIS 全体が `https` 化されましたので、`qwc-docker/docker-compose.yml` においてコメントアウトされている環境変数 `JWT_COOKIE_SECURE: 'True'` の設定を有効にします。

```diff_yaml:docker-compose.yml
...
    x-qwc-service-variables: &qwc-service-variables
      JWT_SECRET_KEY: $JWT_SECRET_KEY
      JWT_COOKIE_CSRF_PROTECT: 'True'
      JWT_COOKIE_SAMESITE: 'Strict'
      # Uncomment if the application is exclusively run over HTTPS to ensure cookies are only sent with HTTPS requests
-     #JWT_COOKIE_SECURE: 'True'
+     JWT_COOKIE_SECURE: 'True'
      ...
      qwc-auth-service:
        image: sourcepole/qwc-db-auth:latest-2025-lts
        environment:
          <<: *qwc-service-variables
          SERVICE_MOUNTPOINT: '/auth'
          # Enable the following in production (requires https):
-         #JWT_COOKIE_SECURE: 'True'
+         JWT_COOKIE_SECURE: 'True'
          #TOTP_ENABLED: 'False'
          ...
```

## 6. 翻訳

### 6-1. マニュアル

ラベルやメッセージの翻訳を修正・追加する方法については、下記にマニュアルがあります。

- [QWC2 Viewer Configuration](https://qwc-services.github.io/master/configuration/ViewerConfiguration/) > [Translations](https://qwc-services.github.io/master/configuration/ViewerConfiguration/#translations)

しかし、このマニュアルに記載されている手順は `QWC2` スタンドアロン環境を前提にしているようで、`qwc-services` 環境での翻訳の修正・追加は実際にどうやったら良いのか、よく分りません。

### 6-2. 翻訳の選択的な上書き？

唯一、"Selectively overriding translation strings" の節が `qwc-services` 環境での適用方法を記述しています。翻訳を選択的に上書き出来るというもので、これは有難いと思ったのですが、指示通りにやってみてもうまく行きませんでした。やり方が間違っているのかも知れませんが、何の変化も無いのです。

### 6-3. QWC2 をカスタム・ビルドして使用する？

どうも、`qwc-services` の一部として動作させる場合の `QWC2` はビルド済みのもの、すなわち、スタンドアロン環境で `yarn run prod` を実行した後の `prod` ディレクトリに生成されるものである必要があるようです。

マニュアルの "Compiling an application bundle" の節を読むと、次のように説明されています。

> 望むようにソースを編集したら、運用環境用のデプロイ可能なアプリケーション・バンドルをコンパイルします。
>
> ```bash
> yarn run prod
> ```
> `qwc-docker` を使う場合は、`qwc2-app/prod` フォルダーのコンテンツを `qwc-docker/volumes/qwc2` にコピーし、そのカスタム・ビルドを `qwc-map-viewer-base` とともに使用するように `qwc-docker/docker-compose.yml` を修正します。
>
> ```yaml
> qwc-map-viewer:
>   image: sourcepole/qwc-map-viewer-base:vYYYY.MM.DD
>   environment:
>     <<: *qwc-service-variables
>     SERVICE_MOUNTPOINT: '/'
>   volumes:
>     - ./pg_service.conf:/srv/pg_service.conf:ro
>     - ./volumes/config:/srv/qwc_service/config:ro
>     - ./volumes/qwc2:/qwc2:ro

これって、要するに、スタンドアロン環境でビルドした `QWC2` を使え、ということですよね。

そりゃ、まあ、そうやれば、翻訳の修正や追加は思い通りに出来るでしょう。しかし、そんな面倒くさいこと出来ますか？ 私はイヤです。

### 6-4. 翻訳ファイルを共有する

結局のところ、最も手軽で実際的な方法は、`QWC2` の翻訳ファイル全てをコンテナとホストで共有するという荒っぽい方法でしょうか。

```diff_yaml
      qwc-map-viewer:
        image: sourcepole/qwc-map-viewer:latest-2025-lts
        environment:
          <<: *qwc-service-variables
          SERVICE_MOUNTPOINT: '/'
        volumes:
          - ./pg_service.conf:/srv/pg_service.conf:ro
          - ./volumes/config:/srv/qwc_service/config:ro
+         - ./volumes/qwc2/translations:/qwc2/translations:ro
          - ./volumes/qwc2/assets:/qwc2/assets:ro
```

`qwc-docker/volumes/qwc2/translations` フォルダには、最新の `qwc2` の翻訳ファイルを `github` から取得してきて設置し、適宜、内容を修正・追加します。

この方法では、今後、`github` において `qwc2` の翻訳が更新された場合にちょっと困る（手作業による更新作業が必要になる）訳です。まあ、それぐらいは大目に見ようという、だからこれは「荒っぽい」方法です。

このあたり、エレガントな方法をご存知の方がいらしたら、是非ご教示ください。

## 7. スタンドアロン環境との小さな相違点

`qwc-services` による地図ビューワを動かしていると、いくつかスタンドアロン環境との相違点に気付きます。本格的な作業に入る前に、ちょっと見ておきます。

### 7-1. 「線を計測」と標高プロファイル

「線を計測」ツールを実行すると、下の図のように、線の長さだけでなく、通過点の標高データが自動的に出力されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/34022d32-26db-46f4-999f-12fe926e6cca.png)

これは、図のような山間地にあっては、非常に魅力的な機能です。

しかし、精度が悪すぎます。

図では、集落の北端に位置する山頂と、南端に位置する谷の狭隘部を通過する直線を引いて、標高プロファイルを表示させています。それぞれの標高が 770m および 419m と表示されていますが、実際には 1,005m と 267m です。こんなに差があっては実用になりません。

原因は、`qwc-elevation-service` が使用している標高取得用データにあります。

`qwc-docker/volumes/config/default/elevationConfig.json` に設定があります。

```json:elevationConfig.json
{
  "$schema": "https://github.com/qwc-services/qwc-elevation-service/raw/master/schemas/qwc-elevation-service.json",
  "service": "elevation",
  "config": {
    "elevation_dataset": "/vsicurl/https://data.sourcepole.com/srtm_1km_3857.tif"
  }
}
```

データ・ファイルは、ファイル名から推測して、形式は `geoTIFF`、`CRS` は `EPSG:3857 WGS 84 / Pseudo-Mercator`、そして `1km` という非常に粗いメッシュ・データであるようです。ダウンロードして `QGIS` で開いてみたらその通りでした。

このデータ・ファイルを差し替えれば良いのだろう、という事で、`isg-dem-3857.tif` というファイルを用意して、それを読み込ませるために、いろいろと試してみました。

- `elevationConfig.json` の書き換え ... ダメ
    - `"elevation_dataset"` の値を書き換えても、`Generate service configuration` ボタンを押すたびに元に戻される
- `config-in/default` に `elevationConfig.json` を置く ... ダメ
    - `config/default` の `elevationConfig.json` は変化しない
- 環境変数 `ELEVATION_DATASET` でデータ・ファイルを指定 ... OK!!

`qwc-docker/docker-compose.yml` の `qwc-elevation-service` を以下のように編集すると、データ・ファイルの差し替えが出来ました。

```diff_yaml:docker-compose.yml
      qwc-elevation-service:
        image: sourcepole/qwc-elevation-service:latest-2025-lts
        environment:
          <<: *qwc-service-variables
          SERVICE_MOUNTPOINT: '/api/v1/elevation'
+         ELEVATION_DATASET: '/isg-dem-3857.tif'
        volumes:
          - ./volumes/config:/srv/qwc_service/config:ro
+         - ./volumes/elevation/isg-dem-3857.tif:/isg-dem-3857.tif:ro
```

`isg-dem-3857.tif` は、`qwc-docker/volumes/elevation` というディレクトリを作って、そこに置いています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/21676ae9-b95a-4c22-823f-f202d784ef8a.png)

これで正確な標高を示すようになりました。

`isg-dem-3857.tif` は兵庫県が提供している 1m メッシュ・データを元にして作成したものですので、精度は十分です。ただし、目が細かい分、カバーしている範囲は小さくなっていて、集落とその周辺だけしか対応できません。

なお、標高データ用 `GeoTIFF` の `CRS` は `EPSG:3857 WGS 84 / Pseudo-Mercator` でなければならないようで、`EPSG:6673 JGD2011 / Japan Plane Rectangular CS V` では正常に動作しませんでした。

### 7-2. 外部レイヤの透明度

次の図は、スタンドアロン環境の `QWC2` の画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/a0a28d09-d69e-4234-831c-59b4bcf35693.png)

「棚田」というレイヤの透明度を設定したつもりですが、無視されています。

`QGIS Server` から直に取得するレイヤでは透明度の設定が効くのですが、`MapCache` から取得する外部レイヤでは、スライドを動かしても何の効果もありません。

そして、次は `qwc-services` 環境でのビューワの画面です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/63ea30af-004b-4c04-8db5-ac6d9b833fec.png)

同じく `MapCache` から取得している外部レイヤなのですが、こちらでは透明度の設定が効いています。

これは嬉しいですね。

### 7-3. 地物検索

`qwc-services` 環境のビューワではメニューに「地物検索」がありませんが、`qwc-docker\volumes\config-in\default\config.json` に `FeatureSearch` を追加することで、スタンドアロン環境と同様に「地物検索」のパネルを使用することが出来ます。

```diff_json:config.json
{
        ...
        "plugins": {
            "common": [
                ...
+               {
+                   "name": "FeatureSearch"
+               },
                ...
                {
                    "name": "TopBar",
                    "cfg": {
                        "menuItems": [
                            ...
+                           {
+                               "key": "FeatureSearch",
+                               "icon": "search"
+                           },
                            ...
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/6783aebc-eb86-48f5-ad60-262ad09f1e1a.png)

## And so, what's next?

何か、あっけない感じですけれど、`qwc-services` による Web GIS はこれで動くようになりました。

最初から `qwc-services` を利用したら良かったのに、とも思いますが、`QGIS Server` + `QWC2` + `MapCache` で一から組み上げた経験があるからこそ、統合環境が簡単で便利に見えるという面もあるでしょう。

今のところ、この Web GIS は、スタンドアロンの `QWC2` ビューワと同等の機能しか備えていません。

次回以降、`qwc-services` で拡張された機能について調べていきます。

最初は「ユーザの認証と権限」です。

- [qwc-services による Web GIS の構築 #2 認証と権限](https://qiita.com/softark/items/e307645aebcd5366d16b)

## おまけ - 運用中の実例

兵庫県の中山間地、棚田の里-岩座神の地図です。

- [i-GIS 岩座神の地図](https://gis.isarigami.net/)

今はまだ、一般に公開して良いデータしか表示していませんが、ゆくゆくは、農地や山林の管理に役立つデータも追加していきたいと考えています。例えば、農地については、地番・面積・所有者・耕作者・作付け状況などのデータが表示できれば良いなあ、と。しかし、そうなると、公開するユーザを限定しなければいけません。ですので、「ユーザの認証と権限」の機能は必須の要件となるわけです。

おいおいと内容を充実して、HINATA GIS に負けない Web GIS に育てていきたいと思っています。（無理無理）