QGIS データを Web で表示 #3 - QGIS Server

QGIS で作成したデータ（"プロジェクト"）を Web ページで表示する方法について、ノウハウを共有します。

初回の [QGIS データを Web で表示 #1 - qgis2web](https://qiita.com/softark/items/b625175f182694e4836e) では、QGIS のプラグインである qgis2web を使用して、QGIS で作成したデータを表示するためのスタンドアロンな Web 地図を作成しました。

これは QGIS プロジェクトのある時点でのスナップショットを切り出して、OpenLayers または Leaflet という地図データ表示 javascript が理解可能なデータを作成して利用する、ある意味で静的な Web 地図を作成するものでした。ウェブ・サーバ上に QGIS のプロジェクトやデータ・ファイルを保有する必要はありませんが、ローカルに保持する QGIS プロジェクトに拡張や変更があるたびにスナップショットを取り直して、Web 地図のページを再作成する必要があります。

## 0. 全体の構想

今から構築しようとしている Web 地図システムにおいては、QGIS のプロジェクトとデータ・ファイルそのものをインターネット上のサーバに置きます。そして、次の3つのモジュールを連携させて Web 地図を表示します。

1. QGIS Server
2. QWC2 (QGIS Web Client 2)
3. MapCache

最初の二つ、すなわち QGIS Server と QWC2 を連携させれば Web 地図の表示が可能になります。しかし、それだけでは地図の表示速度に問題が出るため、二つの間にキャッシュ・サーバを置いてパフォーマンスを改善します。

この作業をモジュールごとに何回かに分けて説明します。今回は QGIS Server、次回とその次の回は QWC2、そして最後に MapCache です。

なお、これについては、Docker を使って QGIS Server と QWC2 を核とした統合的な環境である qwc-services をインストールして利用する方法もあり、むしろ、そちらの方が推奨されています。（[Quick start - Running QWC2 as part of qwc-services](https://qwc-services.github.io/master/QuickStart/) を参照）。しかし、ここでは、それぞれを個別にインストールして、手作業で連携させていきます。そうしなければならない理由は無いのですが、一度は個別のインストールと設定の作業を経験して、各モジュールに対する理解を深めておく方が良いと考えたからです。

## 1. 環境 : AlmaLinux 9.5

AlmaLinux 9.5 の最小限インストール（Minimal Install）から開始します。

QGIS Server を解説している文書では Debin/Ubuntu 系の linux を前提にしている場合が多いのですが、AlmaLinux です。これは CentOS から AlmaLinux へと RHEL 系の linux を使い続けてきて慣れているという私の個人的な事情によります。

読者として linux のインストールとパッケージ管理について最小限度の知識と経験がある人を想定していますので、`dnf` や `vi` の使い方などに関しては詳しくは述べません。

なお、サーバ名は `webgis.mydomain`、アドレスは `192.168.0.9`、常用するユーザ名は `my-account` であると仮定します。

### 1-1. 追加設定

AlmaLinux インストール直後に、私はいくつかの追加の設定を行っていますが、今回の作業に関係する項目だけを挙げます。

#### 1-1-1. SELinux を無効化する

これは推奨されないことですが、よく知らないし、いろいろと面倒くさいので、SELinux を無効化します。ごめん。やり方を知っている人は SELinux を無効化せずに進めて下さい。

`/etc/selinux/config` を下記のように修正します。

```conf
# SELINUX=enforcing
SELINUX=disabled
```

再起動が必要です。

```bash
sudo reboot
```

#### 1-1-2. EPEL レポジトリを追加する

```bash
sudo dnf install epel-release
```

#### 1-1-3. CRB レポジトリを有効化する

```bash
sudo dnf config-manager --set-enabled crb
```

以前の PowerTools に相当する CRB レポジトリを有効化していないと、QGIS Server のインストールで依存の解消が出来なくて、二進も三進も行かなくなります。

## 2. QGIS Server

[QGIS Server Guide/Manual](https://docs.qgis.org/3.34/en/docs/server_manual/index.html) を参照しながら、作業を進めます。

### 2-1. QGIS Server をインストールする

QGIS Server を  `dnf` でインストールします。

```bash
sudo dnf install qgis-server
```

これによって QGIS Server が依存する QGIS も一緒にインストールされます。下記のコマンドで QGIS の存在を確認することが出来ます。

```bash
which qgis
qgis -v
```

`qgis_mapserv.fcgi` がインストールされた場所を確認します。

Debian/Ubuntu 系の linux では `/usr/lib/cgi-bin/qgis/qgis_mapserv.fcg` としてインストールされるようですが、AlmaLinux の場合は `/usr/libexec/qgis/qgis_mapserv.fcg` です。

次のコマンドを実行して、FCGI サーバとして動作することを確認して下さい。

```bash
/usr/libexec/qgis/qgis_mapserv.fcgi
```

この段階では、先ほどの `qgis -v` で表示されたバージョンの QGIS が起動されたことさえ確認できれば、何らかのエラーが表示されても気にする必要はありません。

### 2-2. Xvfb をインストールする

Minimal Install でインストールした環境では X Window が使えません。QGIS / QGIS Server では地図の描画処理（特に印刷）に X Window のフレーム・バッファを使用しますないので、代りに、Xvfb (X virtual frame buffer) をインストールします。

Server with GUI などでインストールした環境では X Window が使えるので Xvfb をインストールする必要はありません。

```bash
sudo dnf install Xvfb
```

`/etc/systemd/system/xvfb.service` を以下の内容で作成します。

```conf
[Unit]
Description=X Virtual Frame Buffer Service
After=network.target

[Service]
ExecStart=/usr/bin/Xvfb :99 -screen 0 1024x768x24 -ac +extension GLX +render -noreset

[Install]
WantedBy=multi-user.target
```

上記で `:99` が Xvfb のディスプレイ番号です。

Xvfb サービスを起動し、自動起動するように設定します。

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

### 2-3. QGIS プロジェクトを設置する

QGIS Server で使用する QGIS プロジェクトをサーバ上に設置します。

ディレクトリ構成については、二つの案が考えられます。

#### 2-3-1. ディレクトリ構成案(1) 効率重視型

```
基準ディレクトリ (例えば "/home/my-account/qgis-projects")
    + common (プロジェクト間共有ディレクトリ）
        + rasters
        + shpfiles
    - project_1.qgs
    + project1 (プロジェクト1 専用のディレクトリ）
        + rasters
        + shpfiles
    - project_2.qgs
    + project2 (プロジェクト2 専用のディレクトリ）
        + rasters
        + shpfiles
    ...
```

- `common` ディレクトリを作って、プロジェクト間で共有するデータを入れる
- プロジェクト・ファイル (`*.qgs`) は、すべて、基準ディレクトリに入れる
- プロジェクトと同名のディレクトリを作って、プロジェクト固有のデータを入れる

長所:
- 効率的

短所:
- データの共有に関する問題が生じやすい
- 基準ディレクトリ直下にファイルが散乱しやすい

#### 2-3-2. ディレクトリ構成案(2) 安全重視型

```
基準ディレクトリ (例えば "/home/my-account/qgis-projects")
    + project_1
        - project_1.qgs
        + rasters
        + shpfiles
    + project_2
        - project_2.qgs
        + rasters
        + shpfiles
    ...
```

- 基準ディレクトリ内にプロジェクトと同名の専用ディレクトリを作る
- 専用ディレクトリ内に、プロジェクト・ファイルと全てのデータ・ファイルを入れる

長所:
- プロジェクト間の干渉を防げる

短所:
- 非効率的

#### 2-3-3. ディレクトリ構成

一長一短ですが、ここでは、第1案を採用します。

#### 2-3-4. デモ・プロジェクトを配置する

自分で作ったプロジェクトを設置しても良いのですが、QGIS Server で利用するためには、QGIS プロジェクト側でいくつか必要な設定があります。（後述します）

この段階では、必要な設定が済んでいるプロジェクトを使用したいので、QGIS の公式レポジトリで提供されているデモ・プロジェクトを使用することにします。

まず、`wget` と `unzip` が必要なので、インストールしておきます。

```bash
sudo dnf install wget
sudo dnf install unzip
```

プロジェクト基準ディレクトリに移動します。

```bash
cd $HOME/qgis-projects
```

zip ファイルを取得して展開し、必要なものを設置します。

```bash
wget https://github.com/qgis/QGIS-Training-Data/archive/release_3.34.zip
mv QGIS-Training-Data-release_3.34/exercise_data/qgis-server-tutorial-data/world.qgs ./.
mv QGIS-Training-Data-release_3.34/exercise_data/qgis-server-tutorial-data/naturalearth.sqlite ./.
```

以上で、次のようにデモ・プロジェクトが設置されます。

```
基準ディレクトリ (例えば "/home/my-account/qgis-projects")
    - world.qgs
    - naturalearth.sqlite
```

このプロジェクトのデータ・ファイルは `sqlite` データベース・ファイルなので、`dnf` で `sqlite` をインストールします。

```bash
sudo dnf install sqlite
```

### 2-4. nginx をインストールする

QGIS Server を供用するためには HTTP サーバが必要ですので、nginx をインストールします。httpd (Apache2) でも良いのですが、慣れている nginx を使う事にします。

AlmaLinux の標準パッケージにも少し古いバージョンの nginx が含まれています。実用上はそれで十分だと思いますが、最新版をインストールするために、以下の内容で `/etc/yum.repos.d/nginx.repo` を作成します。

```conf
[nginx-stable]
name=nginx stable repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=1
enabled=1
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true

[nginx-mainline]
name=nginx mainline repo
baseurl=http://nginx.org/packages/mainline/centos/$releasever/$basearch/
gpgcheck=1
enabled=0
gpgkey=https://nginx.org/keys/nginx_signing.key
module_hotfixes=true
```

参照 : [nginx: Linux packages](https://nginx.org/en/linux_packages.html#RHEL)

そして、`dnf` でインストールします。

```bash
sudo dnf install nginx
```

nginx を起動し、自動起動するように設定します。

```bash
sudo systemctl start nginx
sudo sustemctl enable nginx
```

### 2-5. ファイアウォールを設定する

この段階で、ブラウザでサーバ（例えば `http://webgis.mydomain/`）にアクセスしてみると、接続に失敗すると思います。システムを Minimal Install で始めたので、ファイアウォールが `http` を通さない設定になっている筈だからです。

ファイアウォールの設定を確認します。

```bash
sudo firewall-cmd --list-all
```

services: のリストに `http` という文字が出て来ない場合は、`http` サービスが通るようにファイアウォールの設定を変更する必要があります。

```bash
sudo firewall-cmd --add-service=http --permanent
sudo systemctl restart firewalld
```

`firewall-cmd --list-all` で設定を再確認し、`http://webgis.mydomain/` にブラウザでアクセスして、nginx の Welcome ページが表示されることを確かめます。

### 2-6. Systemd の socket と service を設定する

nginx から FastCGI プロセスとして QGIS Server を呼び出すために、Systemd の socket ユニットと service ユニットを使用します。

FastCGI プロセスの呼び出しには spawn-fcgi または fcgiwrap を使用しても良いのですが、Systemd を使用する方法が最も簡明・堅牢かつ高性能です。

また、QGIS プロジェクトごとに専用の socket と service を使用したいので、Systemd のテンプレート・ユニットを使います。

#### 2-6-1. qgis-server sockets

以下の内容で `/etc/systemd/system/qgis-server@.socket` を作成します。

```conf
[Unit]
Description=QGIS Server socket for %i
PartOf=qgis-server@%i.service

[Socket]
Accept=false
ListenStream=/run/qgis-server-%i.sock
SocketUser=nginx
SocketGroup=nginx
SocketMode=0600

[Install]
WantedBy=sockets.target
```

これはテンプレートになっていて、実際に作成される socket は `%i` を指定のプロジェクト名で置き換えたものになります。例えば、QGIS プロジェクト名が `world` であれば、`/run/qgis-server-world.sock` となります。この socket が対応する `qgis-server@world.service` を起動して呼び出します。

#### 2-6-2. qgis-server services

以下の内容で `/etc/systemd/system/qgis-server@.service` を作成します。

```conf
[Unit]
Description=QGIS Server instance (%i
After=network.target

[Service]
User=nginx
Group=nginx
StandardOutput=null
StandardError=journal
StandardInput=socket
ExecStart=/usr/libexec/qgis/qgis_mapserv.fcgi
Environment=QGIS_PROJECT_FILE=/home/my-account/qgis-projects/%i.qgs
Environment=QGIS_SERVER_LOG_STDERR=1
Environment=QGIS_SERVER_LOG_LEVEL=0
Environment=QGIS_SERVER_CACHE_DIRECTORY=/var/cache/qgis-server
Environment=DISPLAY=:99

[Install]
WantedBy=multi-user.target
```

これもテンプレートになっていて、実際に開始される service は `@` の後ろに指定した QGIS プロジェクト名を追加したもの、例えば `qgis-service@world.service` となります。

- QGIS_PROJECT_FILE ... `%i` が指定したプロジェクト名に置き換わります
- QGIS_SERVER_LOG_LEVEL ... 運用環境では `3` を推奨します
- QGIS_SERVER_CACHE_DIRECTORY ... nginx が読み書き可能なディレクトリを用意しておきます
- DISPLAY ... Xvfb のディスプレイを指定します（Xvfb を使わない場合はこの行を削除）

QGIS_PROJECT_FILE は絶対パスで指定します。このパスの途中にシンボリック・リンクが含まれているとプロジェクト・ファイルを開くときにエラーが出るようなので注意して下さい。

#### 2-6-3. socket と service の起動

今回使用する QGIS プロジェクトは 'world' という名前ですので、次のようにして socket と service の自動起動を設定しておきます。

```bash
sudo systemctl daemon-reload
sudo systemctl enable qgis-server@world.socket
sudo systemctl enable qgis-server@world.service
```

そして、socket と service を起動します。

```bash
sudo systemctl start qgis-server@world.socket
sudo systemctl start qgis-server@world.service
```

service の方は socket から最初に呼び出されたときに自動的に起動されますので、手動で起動しなくても構いません。

### 2-7. nginx によるルーティングを設定する

#### 2-7-1. ファイアウォールの 8082 番ポートを開ける

QGIS Server へのアクセスをルーティングするために、QGIS Server へのゲートウェイをデフォルトの 80 番ポートではなく、8082 番ポートに変更します。そのために、ファイアウォールの設定を変更します。

この際、ついでですから、今後使用する予定のポートも開けておきます。

```bash
sudo firewall-cmd --add-port=8080/tcp --permanent
sudo firewall-cmd --add-port=8081/tcp --permanent
sudo firewall-cmd --add-port=8082/tcp --permanent
sudo systemctl restart firewalld
```
- 8080 ... MapCache ルーティング用
- 8081 ... QWC2（開発版）用
- 8082 ... QGIS Server ルーティング用

#### 2-7-2. qgis-server.conf

`/etc/nginx/conf.d/qgis-server.conf` を以下の内容で作成します。

```nginx
server {
    listen       8082;
    server_name  webgif.mydomain;

    gzip off;
    include fastcgi_params;

    # CORS を許可
    add_header 'Access-Control-Allow-Origin' '*';
    add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
    add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
    add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    
    location /world/ {
        fastcgi_pass unix:/run/qgis-server-world.sock;
    }

    error_log /var/log/nginx/qgis-server-error.log;
    access_log /var/log/nginx/qgis-server-access.log;

    location / {
        root   /usr/share/nginx/html;
        index  index.html index.htm;
    }
}
```

:::note warn
##### CORS 許可が必要です
CORS の許可をしなくても `GetMap`（地図画像取得）リクエストは通りますが、`GetFeatureInfo`（地物情報取得）リクエストが通りません。
:::

nginx を再起動します。

```bash
sudo systemctl restart nginx
```

### 2-6. 動作確認

ブラウザから次のアドレスにアクセスします。

```http
http://webgis.mydomain:8082/world?LAYERS=countries&SERVICE=WMS&VERSION=1.3.0&REQUEST=GetMap&CRS=EPSG:4326&WIDTH=800&HEIGHT=400&BBOX=-90,-180,90,180
```

お疲れ様でした。国別に色分けされた世界地図が表示されたら、QGIS Server が正常に動作している証拠です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/f4a16588-85a5-4868-38bd-f3793e6b63e6.png)

うまく表示されない場合は、以下の方法で原因を探って下さい。

- `/var/log/nginx/` 以下のログ・ファイルを見る
- `journalctl -xe -t qgis_mapserv.fcgi` で、qgis_mapserv.fcgi に関連するログを調査する

## 3. QGIS プロジェクトの設定

自分で作成した QGIS プロジェクトを QGIS Server で使用するためには、いくつかの変更が必要です。

### 3-1. 一般的な注意

#### 3-1-1. データ・ファイルを「外」に置かない

プロジェクトで使用するデータ・ファイルは、プロジェクト・ファイルが置かれているディレクトリまたはそのサブ・ディレクトリに置くのが良いでしょう。

QGIS のプロジェクト・ファイルはデータ・ファイルを相対パスで管理します。従って、データ・ファイルがプロジェクト・ファイルのディレクトリの「外」にある場合、例えば、`../other-project/.` や、`../common-data/.` というようなディレクトリにある場合であっても、位置関係を維持しながらそれらのディレクトリをコピーすれば、おそらく大丈夫でしょう。確認は取っていません。しかし、運用時、特にデータ・ファイルを更新するときなどに、コピーし忘れたりする問題が起きやすいことも事実でしょう。

結局は、プロジェクトごとに独立したディレクトリを持つようにするのが、一番安全です。

今回は、全てのプロジェクト・ファイルを単一の基準ディレクトリ直下に置き、参照するファイルをプロジェクト間で共有するものとプロジェクト専用のものに分けて、サブ・ディレクトリに置いています。共有するファイルが別のプロジェクトから想定外の変更を加えることが無いように注意してください。

#### 3-1-2. *.qgz ではなく *.qgs の形式を使う

プロジェクト・ファイルの形式は、圧縮形式である `*.qgz` ではなく、`*.qgs` にする方が良いでしょう。`*.qgz` が使えない訳ではありませんが、`*.qgs` にする方が楽です。

### 3-2 プロジェクトのプロパティ

QGIS プロジェクトには、QGIS Server などの外部プログラムからのアクセスを制御するプロパティが存在します。これを適切に設定しないと、QGIS Server から利用することが出来ません。

詳細については、マニュアルの [2.4 Configure your project](https://docs.qgis.org/3.34/en/docs/server_manual/getting_started.html#configure-your-project) に書かれていますが、ここでは、重要だと思われるものに絞って説明します。

編集が必要な項目は `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー` および `メニュー・バー > レイヤ > レイヤのプロパティ... > QGIS サーバー` にあります。

#### 3-2-1. Service Capabilities

- `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > Service Capabilities`
    - `Service Capabilities を有効化` を必ず ON にする
    - `短い名前` ... 必ず設定する（例えば "world"）
    - `タイトル` ... 必ず設定する（例えば "世界地図"）
    - `要約` ... 設定を推奨
    - `キーワード` ... 設定を推奨
    - その他は放置して可

#### 3-2-2. WMS

- `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > WMS`
    - `Advertise 範囲` を必ず ON にする
        - `範囲` の設定方法
            - マウス・ボタンを押して、画面上で領域を指定
            - ドロップダウン・リスト
                - レイヤから計算
                    - 対象レイヤを選択
                - レイアウトマップから計算
                - ブックマークから計算
                - 現在のキャンバス領域を利用
    - `レイヤの除外`
        - `＋` ボタンを押して、除外したいレイヤを選択して追加
        - XYZ などの外部サービスを利用する背景レイヤは除外して良い
    - `レイヤと地物のオプション`
        - 地物情報の返答に KKT ジオメトリを加える ... `ON`
    - その他は放置して可

`Adgertise 範囲` は、プロジェクトが想定する初期表示範囲です。

#### 3-2-3. WMTS

- `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > WMTS`

「出力されたレイヤ」と「発行済み」はほとんど誤訳です。それぞれ「出力レイヤ」と「発行（公開）」と読み替えてください。

それぞれのレイヤについて、発行（公開）するかどうか、形式として PNG および JPEG をサポートするかどうかを設定します。

#### 3-2-4. WFS/OAPIF

- `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > OAPIF`

ここでも「発行済み」は「発行（公開）」と読み替えてください。

それぞれのレイヤについて、発行（公開）するかどうか、更新・挿入・削除をサポートするかどうかを設定します。ジオメトリの精度は規定値の `8` のままで良いでしょう。

#### 3-2-5. WCS

- `メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > WCS`

ここでも「発行済み」は「発行（公開）」と読み替えてください。

それぞれのレイヤについて、発行（公開）するかどうかを設定します。一般に、背景レイヤは除外するのが良いでしょう。

#### 3-2-6. レイヤごとのプロパティ

- `メニュー・バー > レイヤ > レイヤのプロパティ... > QGIS サーバー`
    - 説明
        - 短い名前 ... 必ず設定する（例えば "spots"）
        - タイトル ... 必ず設定する（例えば "注目スポット"）
        - WFS title ... ベクタ・レイヤのみ。放置して可
        - 要約 ... 設定を推奨
        - キーワード ... 設定を推奨
    - その他は放置して可

#### 3-2-7. ベクタ・レイヤのフィールド

`メニュー・バー > レイヤ > レイヤのプロパティ... > フィールド` を開くと、下のような画面になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/5d60e86a-aa32-4168-87f8-fd56b753e23c.png)

それぞれのフィールドについて、`設定` のカラムのドロップダウンで、公開する必要の無いもの、公開してはいけないものについて、チェックを入れます。

- ドロップダウン
    - Not searchable
    - WMS 経由で公開しない
    - WFS 経由で公開しない

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/b1609f2d-ffda-4fe3-858d-ba7e545416a5.png)

ここでは、"スポット" のレイヤについて、"名称" フィールドだけを公開する設定にしています。

### 3-3. フォント

ベクタ・レイヤで文字表示に使用するフォントは linux でも使えるものに変更しておく必要があります。そうしないと地物のラベルなどが □□□□ のような豆腐の連続になります。

お奨めは Google の Noto フォントです。これなら AlmaLinux の最小インストールでも最初から入っています。Windows では入っていませんので QGIS Desktop を Windows で使用している場合はインストールする必要があります。

### 3-4. プロジェクトの CRS

QGIS Server 単体で考える時は、プロジェクトの CRS は何でも良いでしょう。

私は `ESPG:6673 JGD2011 / Japan Plane Rectangular CS V` を良く使用します。これは、私が作成する地図の表示範囲が兵庫県の内陸部であるため、その適用対象範囲から見てこれが良いだろう（同じ範囲でも `JGD2000` ではなくて、新しい方の `JGD2011` だよな）と考えて選んだ結果です。

しかし、QWC2 との連携、特に MapCache との連携を念頭に置いた場合は、`ESPG:3857 WGS 84 Pseudo-Mercator` を選んでおく方が、後々、作業がスムーズに出来ます。

各レイヤの CRS はそのままにしておいて、プロジェクトの CRS だけを `ESPG:3857` に変更するのが良いと思われます。

## And so, what's next?

次回は QWC2 (QGIS Web Client 2) をインストールして、QGIS Server と連携させ、一通り動く Web 地図システムを作成します。

- [QGIS データを Web で表示 #4 - QWC2](https://qiita.com/softark/items/88ac73163cff278063a3)