QGIS データを Web で表示 #5 - QWC2（続）

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

前回は QWC2 (QGIS Client 2) をセットアップして、前々回にセットアップした QGIS Server と接続し、QGIS で作成したプロジェクトを表示する Web 地図アプリを動かすところまで漕ぎ着けました。

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

前回の最後で述べたように、これで一応 Web 地図アプリは動くようになったのですが、速度の面での問題が大きく、これを改善しないと実用に耐えるものにはなりません。

次回に予定している MapCache の導入によって大幅なパフォーマンス改善を計りたいと思いますが、その前に QWC2 だけで可能な対策がありますので、それを片付けておきます。

マニュアルとして、前回同様、`QWC2 / QWC Services` のドキュメントを参照します。

- [QWC2](https://qwc-services.github.io/master/) > [Quick Start](https://qwc-services.github.io/master/QuickStart/)

また、説明で使用しているプロジェクト "isg" は、下記からダウンロードすることが出来ます。自由にお使いください。

- https://isarigami.net/map/isg.zip

## 1. 外部の XYZ タイルを直接利用する

"isg" プロジェクトでは、背景は外部の XYZ タイルを利用して表示しています。

| XYZ タイル | URL |
|:----------|:----|
| 国土地理院-標準地図 | `https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png` |
| 国土地理院-航空写真 | `https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg` |
| Google Maps | `https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}` |
| Google Satellite | `https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}` |

現在は QWC2 がこれらのレイヤを表示するときに、QGIS Server を経由して画像データを取得しています。

これを QWC2 が直接に外部 XYZ タイルを利用するようにテーマの設定を変更することが出来ます。効果も高く、設定も簡単です。二つの方法があります。

1. 外部レイヤとして XYZ タイルを利用する
2. 背景レイヤとして XYZ タイルを利用する

順に説明します。

### 1-1. 外部レイヤとして XYZ タイルを利用する

外部レイヤは、テーマのサーバとは異なる外部のサービスに依存するレイヤであり、元のレイヤの描画処理を置き換えるものです。

#### 1-1-1. 外部レイヤを定義する

まず、`themesConfig.json` の `"themes"` ノードの下の `"externalLayers"` ノードに、外部レイヤの定義を追加します。後でテーマごとの `"externalLayers"` ノードを追加しますが、それとは別ですので、混同しないようにしてください。

```diff_json
        "externalLayers": [
+           {
+               "name": "gsijp-std",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
+           },
+           {
+               "name": "gsijp-photo",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg"
+           },
+           {
+               "name": "g-map",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"
+           },
+           {
+               "name": "g-sat",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
+           },
            {
                "name": "bauzonen",
                "type": "wms",
                "tiled": true,
                "url": "https://wms.geo.admin.ch",
                "params": {
                    "LAYERS": "ch.are.bauzonen"
                },
                "infoFormats": [
                    "text/plain"
                ]
            }
        ],
```

`"bauzonen"` は `qwc2-demo-app` が最初から定義している外部レイヤです。その前に "isg" で利用する4つの外部レイヤを追加しています。

XYZ タイルを外部レイヤとして登録するためには、上記で示したように、

- `"name"` ... 外部レイヤの識別名
- `"type"` ... 固定値 `"xyz"`
- `"tiled"` ... 固定値 `true`
- `"url"` ... XYZ タイルの `URL`

を指定します。`"tiled"` は指定しなくても良いのかも知れません。

#### 1-1-2. 外部レイヤを使用する

外部レイヤの定義が出来たら、それをテーマで使用します。

`themesConfig.json` のテーマのノードの下に `"externalLaysers"` のノードを作成して、内部レイヤと外部レイヤを結びつけます。

```diff_json
            {
                "id": "isg",
-               "url": "http://gis-svr.vmware:8082/isg"
+               "url": "http://gis-svr.vmware:8082/isg",
+               "externalLayers": [
+                   {
+                       "name": "gsijp-std",
+                       "internalLayer": "gsijp-std"
+                   },
+                   {
+                       "name": "gsijp-photo",
+                       "internalLayer": "gssjp-photo"
+                   },
+                   {
+                       "name": "g-map",
+                       "internalLayer": "g-map"
+                   },
+                   {
+                       "name": "g-sat",
+                       "internalLayer": "g-sat"
+                   }
+               ]
            },
```

上記のように、外部レイヤと内部レイヤを結びつけるには、

- `"name"` ... 定義した外部レイヤの識別名
- `"internalLayer"` ... （テーマの中の）内部レイヤの識別名

を指定します。

ここでは内外二つの識別名が同じになっていますが、異なっていても構いません。ただし、内部レイヤの識別名は QGIS で設定した「レイヤの短い名前」である必要があります。

なお、外部レイヤの定義は、複数のテーマから参照して使い回すことが出来ます。

### 1-2. 背景レイヤとして XYZ タイルを利用する

外部の XYZ タイルを QWC2 で利用するもう一つの方法は、背景レイヤとして利用する方法です。

背景レイヤも、テーマのサーバとは異なる外部のサービスに依存するレイヤですが、元のテーマのレイヤを置き換えるものではなく、背景専用のレイヤを追加するものです。

QWC2 では、背景レイヤはテーマのレイヤ・パネルには表示されず、専用の背景選択ウィジェットによって表示制御されます。（下のスクリーンショットを参照）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/4108de2d-3138-8834-f2c7-479904b8d261.png)

背景レイヤは「背景無し」を含めて、相互排他的なレイヤ・グループになります。

#### 1-2-1. 背景レイヤを定義する

まず、`themesConfig.json` の `"themes"` ノードの下の `"backgroundLayers"` ノードに、外部 XYZ タイルの定義を追加します。後にテーマごとの `"backgroundlLayers"` ノードを追加しますが、それとは別ですので、混同しないようにしてください。

```diff_json
        "backgroundLayers": [
+           {
+               "name": "gsijp-std",
+               "title": "国土地理院-標準地図",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
+           },
+           {
+               "name": "gsijp-photo",
+               "title": "国土地理院-航空写真",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg"
+           },
+           {
+               "name": "g-map",
+               "title": "Google Maps",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}"
+           },
+           {
+               "name": "g-sat",
+               "title": "Google Satellite",
+               "type" : "xyz",
+               "tiled" : true,
+               "url" : "https://mt1.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
+           },
            {
                "name": "mapnik",
                "title": "Open Street Map",
                "type": "osm",
                "source": "osm",
                "thumbnail": "mapnik.jpg",
                "attribution": "OpenStreetMap contributors",
                "attributionUrl": "https://www.openstreetmap.org/copyright"
            },
            ...         
        ],
```

`"mapnik"` は `qwc2-demo-app` が最初から定義している背景レイヤです。その前に "isg" で利用する4つの背景レイヤを追加しています。

XYZ タイルを背景レイヤとして登録する方法は、上記で示したように、外部レイヤの場合とほとんど同じです。

- `"name"` ... 外部レイヤの識別名
- `"title"` ... 外部レイヤのタイトル
- `"type"` ... 固定値 `"xyz"`
- `"tiled"` ... 固定値 `true`
- `"url"` ... XYZ タイルの `URL`

`"thumbnail"` も指定する方が見た目は良いのですが、設定方法の説明は別の機会に譲ります。

#### 1-2-2. 背景レイヤを使用する

背景レイヤの定義が出来たら、それをテーマで使用します。

`themesConfig.json` のテーマのノードの下に `"backgroundLaysers"` のノードを作成して、使用する背景レイヤを指定します。

```diff_json
            {
                "id": "isg",
-               "url": "http://gis-svr.vmware:8082/isg"
+               "url": "http://gis-svr.vmware:8082/isg",
+               "backgroundLayers": [
+                   {
+                       "name": "gsijp-std",
+                       "visibility": true
+                   },
+                   {
+                       "name": "gsijp-photo"
+                   },
+                   {
+                       "name": "g-map"
+                   },
+                   {
+                       "name": "g-sat"
+                   }
+               ]
            },
```

背景を定義する順序は、背景選択ウィジェットに表示される順序に反映されます。また、初期表示する背景レイヤを `"visibility": true` で指定します。どのレイヤも指定しない場合は、「背景無し」となります。

### 1-2-3. 元のプロジェクトから重複するレイヤを除外する

使用する背景レイヤと同じ内容のレイヤをテーマが持っている場合は、重複を避けるために、元の QGIS プロジェクトからそれらのレイヤを除外します。

レイヤ自体を削除する必要は無くて、`WMS` サービスの対象から除外するだけで構いません。

`メニュー・バー > プロジェクト > プロパティ... > QGIS サーバー > WMS > レイヤの除外` を `ON` にして、除外するレイヤを指定します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/1a13204b-e6ce-4f48-49ef-a74b9c432957.png)

`＋` ボタンを押すと、除外するレイヤを選択して追加することが出来ます。上のスクリーンショットでは、レイヤ・グループでまとめて背景レイヤを除外していますが、個別に指定することも可能です。

なお、QGIS プロジェクトに変更を加えた場合は、`qgis-server` サービスを再起動する必要があります。

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

### 1-3. 外部レイヤか、背景レイヤか

どちらの方法でも、外部 XYZ タイルを参照するレイヤを高速化することが可能です。相違点は QWC2 におけるレイヤ構成の違いです。

- プロジェクト本来のレイヤ構成を維持したい場合は外部レイヤ
- 背景専用のレイヤを作ってテーマのレイヤ構成をすっきりさせたい場合は背景レイヤ

とするのが妥当でしょう。

## 2. その他の高速化について

### 2-1. ベクタ・レイヤはベクタとして描画する？

外部 XYZ タイルを直接利用する以外に、QWC2 側で出来る高速化の方法としては、ベクタ・レイヤのデータをそのままベクタとして描画する、という方法が考えられます。

現在、QWC2 は全てのレイヤについて、描画済みの地図画像ファイルを `WMS (Web Map Service)` 経由でサーバから取得して、ウェブ・ブラウザに渡しています。ベクタ・レイヤであっても、`WMS` サーバに描画を実行させています。`WMS` サーバから QWC2 に送信されるのも、QWC2 からウェブ・ブラウザに送信されるのも、基本的に同じ画像ファイルになります。

これに対して、QWC2 が `WFS (Web Feature Servide)` 経由でサーバからベクタ・データを取得し、ウェブ・ブラウザにもベクタ・データを渡して、描画はウェブ・ブラウザ上の `javascript` に任せる、という方法が考えられる訳です。

ベクタ・レイヤの描画を `OpenLayers` または `Leaflet` という `javascript` ライブラリに任せるという手法は、第1回で解説した `qgis2web` でも採用されているものなので、高速化の効果は期待できる筈です。

しかし、このアイデアには、**そういう手法は QWC2 ではサポートされていない**、という障壁があります。少なくとも、公式ドキュメントでは言及されていません。QWC2 の機能拡張は比較的容易なようですが、おそらく、本体部分のコードに手を入れなければ、この手法は実現できないでしょう。

さらに、ベクタ・データを直接描画する手法には、以下のような問題点も存在します。

- データ数が増えるに従って必要な描画時間が増えるので、多数の地物を含むレイヤについては、縮尺等に応じて取得する地物数を制限するなどの工夫が必要
- 描画速度はウェブ・ブラウザが稼働している端末の能力に大きく左右される（これに対してラスタ・データの描画速度は端末の能力にそれほど依存しない）
- 地物のスタイルを元の QGIS プロジェクト通りに再現するのに苦労する（特にラベルの表示スタイルにおいて、`qgis2web` でもそうだったように、かなりの困難が予想される）

高価な有料の Web GIS システムはこのあたりの問題を頑張って克服しているのでしょうが、QWC2 ではこの手法の採用は諦めざるを得ないところです。

### 2-2. 地図画像をキャッシュする？

QWC2 に地図画像をキャッシュする機能があれば良いのですが、残念ながら、ありません。

## And so, what's next?

という次第ですので、パフォーマンスを改善するために、QGIS Server と QWC2 の仲介をする `MapCache` という地図キャッシュ・サーバを導入します。

- [QGIS データを Web で表示 #6 - MapCache](https://qiita.com/softark/items/0279178a484b451b15a5)