QGIS データを Web で表示 #6 - MapCache

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

前回までに QGIS Server を設置し、QWC2 (QGIS Client 2) によって QGIS プロジェクトを表示する Web 地図アプリを作成しました。

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

今回は Web 地図アプリのパフォーマンスを改善するために、QGIS Server と QWC2 の仲介をして、QWC2 による地図画像の表示を高速化する MapCache という地図キャッシュ・サーバを導入します。

## 1. 地図キャッシュ・サーバ

地図キャッシュ・サーバは一種の WMS サーバであり、WMS クライアント（e.g. QWC2）にとっては、WMS サーバ（e.g. QGIS Server）の代理（`Proxy`）として働きます。

### 1-1. 基本的な動作

地図キャッシュ・サーバは、基本的には次のような動作をします。

1. WMS クライアントからリクエストを受ける
2. リクエストに適応するキャッシュ・データの有無を調べる
    - 無い場合のみ
        1. WMS サーバにリクエストを回送する
            - WMS サーバが地図画像を作成して返す 
        2. WMS サーバからデータを受け取る
        3. データをキャッシュに保存する
3. 適応するキャッシュ・データを返す

取得済みのデータが無い場合は、普通に WMS サーバにリクエストするのと大して変らないか、キャッシュ管理の処理がある分だけ余計に時間がかかります。しかし、取得済みのデータが増えていくにつれて、負荷の高い地図画像作成の過程をスキップできるようになるため、全体として、高速化と WMS サーバの負担軽減をはかる事が出来ます。

さらに、たいていの地図キャッシュ・サーバは、必要になる可能性のあるデータをあらかじめ全部取得しておく `"seeding"` すなわち「種蒔き」という機能を持っているため、初回データ取得時の問題も回避することが出来ます。

### 1-2. 製品例

オープン・ソースの地図キャッシュ・サーバとして私の目に付いたのは次の三つです。

- MapCache ... [MapServer](https://qiita.com/drafts/0279178a484b451b15a5/edit) > [MapServer Suite Products](https://mapserver.org/products.html#products) > [MapCache](https://mapserver.org/mapcache/index.html#mapcache)
- MapProxy ... https://www.mapproxy.org/
- TileCache ... http://tilecache.org/

今回使用する MapCache は、MapServer という WMS サーバのスイートに属する地図キャッシュ・サーバですが、単体で使用できます。MapProxy に比べて造りが少し古いかも知れませんが、定評のある製品です。C 言語で書かれています。

MapProxy は Python で書かれたもので、stackvverflow の記事 - [How to cache QGIS Server WMS?](https://stackoverflow.com/questions/31672659/how-to-cache-qgis-server-wms) によると、簡単にセットアップ出来て、QGIS Server との連携も問題ないとのことです。

TileCache については良く知りません。

## 2. MapCache

マニュアルとして、以下の公式ドキュメントを参照します。

- [MapCache 1.14.0](https://mapserver.org/mapcache/index.html)

### 2-1. インストール

マニュアルでは、ソースを取得してコンパイルする方法が説明されていますが、AlmaLinux ではパッケージが提供されていますので、`dnf` でインストールすることが出来ます。

```bash
sudo dnf install libmapcache libmapcache-tools mod_mapcache
```

これによって、以下の三つのパッケージがインストールされます。

- `libmapcache` ... MapCache 本体
- `libmapcache-tools` ... `mapcache_seed` などのツール
- `mod_mapcache` ... `httpd` 用モジュール

これらの依存物として、`mapserver` と `httpd (Apache)` も一緒にインストールされます。今回は `mapserver` は使用しませんが、`httpd` は使用します。

### 2-2. httpd (Apache) の設定

MapCache が想定している http サーバは、`nginx` ではなく、`httpd (Apache)` です。

マニュアルによれば、「`nginx` で動かすのはまだいくらか実験的な意味合いである」とのことですし、さらに `nginx` で動かすとしても `httpd` との併用を推奨していますので、ここは素直に `httpd` だけを使用する MapCache の既定方針に従います。

#### 2-2-1. /etc/httpd/conf/httpd.conf

`/etc/httpd/conf/httpd.conf` を2箇所書き換えます。

##### 2-2-1-1. Listen するポート番号を変更

```diff_apache
  #Listen 12.34.56.79:80
- Listen 80
+ Listen 8080
```

ポート 80 番は `nginx` が既に QWC2 のために使っていますので、ポート 8080 番を代りに使います。

##### 2-2-1-2. mapcache モジュールを登録する

```diff_apache
  Include conf.modules.d/*.conf
+ LoadModule mapcache_module modules/mod_mapcache.so
```

#### 2-2-2. /etc/httpd/conf.d/mapcache.conf

以下の内容で `/etc/httpd/conf.d/mapcache.conf` を作成します。

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

#### 2-2-3. httpd の自動起動を設定する

`httpd` がシステム起動時に自動起動するように設定しておきます。

```bash
sudo systemctl enable httpd
```

通常はこのとき一緒に `httpd` も起動するのですが、それはまだ出来ません。

初期状態の設定に問題があって、今はまだ MapCache が起動できず、従って、MapCache モジュールをロード出来ないために、`httpd` 自体が起動に失敗します。

### 2-3. MapCache の設定

MapCache の設定ファイルは `/etc/mapcache.xml` です。

MapCache のインストールによって `/etc/mapcache.xml` は自動的に作成されているのですが、設定内容に問題がある訳です。

#### 2-3-1. 最低限の設定

MapCache が動くように、初期状態の設定に最低限の修正を加えます。

##### 2-3-1-1. キャッシュ用ディレクトリ

キャッシュ用のディスク領域やロック・ファイルのディレクトリとして `"/tmp"` が指定されている所がいくつかあります。しかし、永続性が保証されない `"/tmp"` を使用するのは適切ではありませんので、`"/tmp"` という記述をすべて `"/var/cache/mapcache"` に変更します。

```diff_xml
  <mapcache>
      <cache name="disk" type="disk">
-         <base>/tmp</base>
+         <base>/var/cache/mapcache</base>
          <symlink_blank/>
      </cache>
      <cache name="sqlite" type="sqlite3">
-         <dbfile>/tmp/{tileset}-{z}-{grid}.db</dbfile>
+         <dbfile>/var/cache/mapcache/{tileset}-{z}-{grid}.db</dbfile>
          <detect_blank/>
      </cache>
      ...
      <locker type="disk">
-         <directory>/tmp</directory>
+         <directory>/var/cache/mapcache</directory>
          <timeout>300</timeout>
      </locker>
      <log_level>debug</log_level>
  </mapcache>

```

ディレクトリ `/var/cache/mapcache` は、`libmapcache` がインストールされた時に自動作成されています。所有者が `root` なので、Apache が起動した MapCache から読み書きが出来るように、所有者とグループを `apache` に変更しておきます。

```bash
sudo chown apache:apache /var/cache/mapcache
```

##### 2-3-1-2. タイルセット t2 を削除

タイルセットが `t1` と `t2` の二つ登録されていますが、サンプルとしては一つで十分なので、`t2` はごっそり削除します。

##### 2-3-1-3. ノード dimensions を削除

タイルセット `t1` に `dimensions` というノードがありますが、その中に `'type="postgresql"'` というエラー原因となる記述が含まれています。

当面、`dimensions` は無くても差し支えがありませんので、ノードごと削除します。

##### 2-3-1-4. sqlite キャッシュを削除

キャッシュのタイプとしては、当面、`disk` が有れば十分ですので、`sqllite` キャッシュの定義を削除し、参照個所を `disk` に変更します。

```diff_xml
-     <cache name="sqlite" type="sqlite3">
-         <dbfile>/var/cache/mapcache/{tileset}-{z}-{grid}.db</dbfile>
-         <detect_blank/>
-     </cache>
      ...
    <tileset name="t1">
       <source>vmap0</source>
-      <cache>sqlite</cache>
+      <cache>disk</cache>
       ...
    </tileset>
```

##### 2-3-1-5. `<service type="wms">` の修正

`<service type="wms">` は以下のように修正します。

```diff_xml
        <service type="wms" enabled="true">
-           <full_wms>assemble</full_wms>
+           <full_wms>error</full_wms>
            <resample_mode>bilinear</resample_mode>
-           <format>JPEG</format>
+           <format allow_client_override="true">PNG</format>
       </service>
```

- `<full_wms>` ... デフォルト値に復帰させます
- `<format>` ... JPEG から PNG に変更します
    - これを `JPEG` のままにしておくと、クライアントが受け取る画像が透過情報を持たない `jpeg` になってしまいます

##### 2-3-1-6. 最低限の mapcache.xml

結局、MapCache が最低限動作するための `mapcache.xml` は以下のようになります。

```xml
<?xml version="1.0" encoding="UTF-8"?>
<mapcache>
    <cache name="disk" type="disk">
        <base>/var/cache/mapcache</base>
        <symlink_blank/>
    </cache>

    <source name="vmap0" type="wms">
        <getmap>
            <params>
                <FORMAT>image/png</FORMAT>
                <LAYERS>basic</LAYERS>
            </params>
        </getmap>
        <http>
           <url>http://vmap0.tiles.osgeo.org/wms/vmap0</url>
        </http>
    </source>

    <tileset name="t1">
        <source>vmap0</source>
        <cache>disk</cache>
        <grid>WGS84</grid>
        <grid>GoogleMapsCompatible</grid>
        <format>PNG</format>
        <metatile>5 5</metatile>
        <metabuffer>10</metabuffer>
        <expires>3600</expires>
    </tileset>

    <default_format>JPEG</default_format>
    
    <service type="wms" enabled="true">
        <full_wms>error</full_wms>
        <resample_mode>bilinear</resample_mode>
        <format allow_client_override="true">PNG</format>
        <maxsize>4096</maxsize>
    </service>
    <service type="wmts" enabled="true"/>
    <service type="tms" enabled="true"/>
    <service type="kml" enabled="true"/>
    <service type="gmaps" enabled="true"/>
    <service type="ve" enabled="true"/>
    <service type="mapguide" enabled="true"/>
    <service type="demo" enabled="true"/>

    <errors>report</errors>
   
    <locker type="disk">
        <directory>/var/cache/mapcache</directory>
        <timeout>300</timeout>
    </locker>
    
    <log_level>debug</log_level>
</mapcache>
```

##### 2-3-1-7. 動作確認

`httpd` を起動します。

```bash
sudo systemctl start httpd
```

`http://webgis.mydomain:8080/mapcache/demo` にアクセスして、動作を確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/7a2b1258-b6f4-5131-6929-64eb9d417bdc.png)

上のような素っ気ない画面が表示されればオーケーです。

さらに、`wms` のリンクをクリックすると、下のような画面が表示されるはずです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/c20fbb57-4315-80a8-53d4-f54ff1fded7a.png)

一応タイルセットは表示されるのですが、サンプルの WMS サーバから地図画像を取得することに失敗しているらしく、ピンクのタイルしか表示されません。私たちのせいではありませんので、これ以上どうしようもありません。気にしなくて良いでしょう。（本当か？）

#### 2-3-2. QGIS Server からデータを取得する

それでは、QGIS Server から `WMS` 経由で地図画像データを取得してキャッシュに保存してみます。

具体的には、QGIS Server から `"isg"` プロジェクトの `"cs"` レイヤ（CS 立体図）の地図画像を取得して、`"cs"` というタイルセットにキャッシュ・データを保存します。

##### 2-3-2-1. cache

キャッシュ媒体は `"disk"` をそのまま使用します。

```xml
    <cache name="disk" type="disk">
        <base>/var/cache/mapcache</base>
        <symlink_blank/>
    </cache>
```

- `<cache>`
    - `name` ... キャッシュの識別名
    - `type` ... 種別（"disk", "sqlite3", "postgresql" など）
    - `<base>` ... ベース・ディレクトリ
        - `/var/cache/mapcache` またはそのサブ・ディレクトリを推奨
        - ユーザ `apache` が読み書きできるように、所有者またはパーミッションを変更すること
    - `<symlink_blank/>` ... 空白タイルはシンボリック・リンクにする

##### 2-3-2-2. grid

キャッシュ管理のキモとなるグリッドを定義します。

下記を `mapcache.xml` に追加します。`cache` の直後が良いでしょう。

```xml
    <grid name="ISG">
        <metadata>
            <title>Grid for isg project</title>
        </metadata>
        <srs>EPSG:3857</srs> <!-- WGS 84 / Pseudo-Mercator -->
        <units>m</units>
        <extent>15013800 4180200 15016600 4183700</extent>
        <resolutions>
            611.4962262814100
            305.7481131407048
            152.8740565703525
            76.43702828517624
            38.21851414258813
            19.10925707129406
            9.554628535647032
            4.777314267823516
            2.388657133911758
            1.194328566955879
            0.5971642834779395
            0.2985821417389697
            0.1492910708694849
            0.0746455354347424
        </resolutions>
        <size>256 256</size>
        <read-only>false</read-only>
    </grid>
```

- `<grid`>
    - `name` ... グリッドの識別名
    - `<metadata>`
        - `<title>` ... グリッドのタイトル
    - `<srs>` ... 座標参照系
        - `EPSG:3857 (WGS 84 / Pseudo-Mercator)` を推奨
        - 私の間違いかも知れませんが、`isg` プロジェクトの `CRS` である `EPSG:6673 (JCG2011 Japan Plane Rectangular CS V)` では、うまく動作しませんでした
        - 対象となるプロジェクトの `CRS` が `EPSG:3857` に変換可能であり、QGIS Server が `EPSG:3857` で地図画像を取得できる必要があります
    - `<units>` ... 座標参照系の単位
        - `EPSG:3857` では `m (meter)` です
    - `<extent>` ... データの範囲
        - `<srs>` で指定した座標参照系における範囲を `xmin(西端) ymin(南端) xmax(東端) ymax(北端)` の順で指定します
    - `<resolutions>` ... サポートする解像度（≒縮尺）
        - 最小縮尺 (Zoom Level 0) から、最大縮尺へ、順に指定します
        - 数値は、画素一つあたりの `<units>` 数（この場合は m 数）を示します
    - `<size>` ... タイルのサイズ
        - `256 256` が一般的です
    - `<read-only>` ... 書き込み禁止
        - 通常は `false` にします
            - `false` ... キャッシュに無いデータを要求されたときは、`WMS` サーバからデータを取得してキャッシュを更新します
            - `true` ... キャッシュに無いデータは「無い」と応答します。`mapcache_seed` による `seeding` のみでキャッシュを更新する運用のための設定です

##### 2-3-2-3. source

以下の `source` 定義を `mapchace.xml` に追加します。

```xml
    <source name="cs" type="wms">
        <getmap>
            <params>
                <VERSION>1.3.0</VERSION>
                <FORMAT>image/png</FORMAT>
                <LAYERS>cs</LAYERS>
                <TRANSPARENT>true</TRANSPARENT>
            </params>
        </getmap>
        <http>
            <url>http://webgis.mydomain:8082/isg</url>
        </http>
    </source>
```

- `<source>`
    - `name` ... ソースの識別名
    - `type` ... ソースのタイプ（通常は "wms"）
    - `<getmap>`
        - `<params>` ... GetMap リクエストに使用する引数
            - `<VERSION>` ... 使用する `WMS` のバージョン
                - `1.1.1` または `1.3.0`
                - デフォルトは `1.1.1`
            - `<FORMAT>` ... 取得する画像タイプ
                - `image/png` または `image/jpeg`
            - `<LAYERS>` ... 取得するレイヤの識別名
                - 通常はレイヤを一つだけ指定します
                - 複数のレイヤをカンマ区切りで指定すると、全部のレイヤが合成された画像を取得します
            - `<TRANSPARENT>` ... 透過部分を持つレイヤかどうか
    - `<http>`
        - `<url>` ... `WMS` サービスの `URL`

##### 2-3-2-4. tileset

最後にタイルセットを定義します。これは、通常、上記の `source` と一対一で対応します。

```xml
    <tileset name="cs">
        <source>cs</source>
        <cache>disk</cache>
        <grid>ISG</grid>
        <format>PNG</format>
        <metatile>5 5</metatile>
        <metabuffer>10</metabuffer>
    </tileset>
```

- `<tileset>`
    - `name` ... タイルセットの識別名
    - `<source>` ... ソースの識別名
    - `<cache>` ... キャッシュの識別名
    - `<grid>` ... グリッドの識別名
    - `<format>` ... クライアントに返すデータ形式
        - 通常は `PNG` または `JPEG`
    - `<metatile>` ... 同時に作成するタイルの範囲
        - `"3 3"`, `"5 5"`, `"7 7"` など
        - ある位置のタイルを要求されたとき、キャッシュに無ければ、`WMS` に要求を出してタイルを作成しますが、そのとき、当該タイルを中心とした一定範囲のデータをまとめて取得して、複数のタイルを同時に作成します。例えば `"5 5"` の場合、当該タイルを中心とした25個のタイルを同時に作成します
        - 隣接するタイルを作っておくことで、スムーズなスクロールを実現します
        - `WMS` に対する要求をまとめることで高速化と負荷軽減を図る意図もあります
    - `<metabuffer>` ... タイル作成時のバッファ
        - ここで指定したピクセル数だけ上下左右に広いデータを `WMS` サーバから取得して、タイルを切り出します
        - タイルの継ぎ目で図形が切れるのを防ぐ効果があります
        - ラベルを使用しているレイヤでは、文字列が切れないように、大きな数値を指定する必要があります

##### 2-3-2-5. デモ・データを削除

デモ・データが残っていると、デモ画面で動作確認がまったく出来ないということではありませんが、`isg` プロジェクトの表示領域から大きくはずれた範囲が初期表示されるため、動作確認に非常に手間取ります。定義の順序を変えても、デモ・データが最初に表示され、その表示領域がデフォルト扱いされるのです。

鬱陶しいので、`mapcache.xml` から `"vmap0"` ソースと `"t1"` タイルセットの定義をごっそりと削除します。

##### 2-3-2-6. デモ画面で動作確認

`httpd` を再起動して、`http://webgis.mydomain:8082/mapcache/demo` にアクセスして、下のスクリーン・ショットのような画面が表示されたら成功です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/d07580b6-944a-8a0f-d77f-c9bad11947ec.png)

ズームイン・ズームアウトやスクロールをして、表示済みの縮尺と範囲でキャッシュが効いていることを確かめてください。

##### 2-3-2-7. キャッシュのディスク領域を確認

`/var/cache/mapcache` 以下にキャッシュ・データが作成されていることを確認してください。

```bash
cd /var/cache/mapcache
find
```

下記のようなリストが出力されるはずです。

```
.
./cs
./cs/ISG
./cs/ISG/06
./cs/ISG/06/000
./cs/ISG/06/000/000
./cs/ISG/06/000/000/000
./cs/ISG/06/000/000/000/000
./cs/ISG/06/000/000/000/000/000
./cs/ISG/06/000/000/000/000/000/000.png
./cs/ISG/06/000/000/000/000/000/001.png
./cs/ISG/06/000/000/001
./cs/ISG/06/000/000/001/000
./cs/ISG/06/000/000/001/000/000
./cs/ISG/06/000/000/001/000/000/000.png
./cs/ISG/06/000/000/001/000/000/001.png
./cs/ISG/blanks
./cs/ISG/blanks/00000000.png
（以下略）
```

キャッシュ・データのディレクトリとファイルは、次の方式で組織化されています。

```
base
  tileset
    grid
      zoom-level
        y-high
          y-middle
            y-low
              x-high
                x-middle
                  x-low.png (or x-log.jpeg)
```

従って、`./cs/ISG/06/000/000/001/000/000/001.png` は

- タイルセット = `cs`
- グリッド = `ISG`
- ズーム・レベル = 6
- Y = 1
- X = 1

の画像データです。

なお、`./cs/ISG/blanks/00000000.png` は、シンボリック・リンクで共有される空白タイルです。

#### 2-3-3. 定義済みグリッド

MapCache は、使用頻度の高いグリッドを定義済みグリッドとして三つ持っています。

1. `WSG84`
2. `GoogleMapsCompatible`
3. `g`

これらについては、わざわざ定義しなくても、識別名を指定するだけで使用することが出来ます。

`/usr/share/mapcache/mapcache.xml.sample` によると、それぞれの内容は以下の通りです。

##### 2-3-3-1. 定義済みグリッド WGS80

```xml
    <grid name="WGS84">
        <metadata>
            <title>GoogleCRS84Quad</title>
            <WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleCRS84Quad</WellKnownScaleSet>
        </metadata>
        <extent>-180 -90 180 90</extent>
        <srs>EPSG:4326</srs>
        <units>dd</units>
        <size>256 256</size>
        <resolutions>
            1.40625000000000 
            0.703125000000000 
            0.351562500000000 
            0.175781250000000 
            8.78906250000000e-2 
            4.39453125000000e-2 
            2.19726562500000e-2 
            1.09863281250000e-2 
            5.49316406250000e-3 
            2.74658203125000e-3 
            1.37329101562500e-3 
            6.86645507812500e-4 
            3.43322753906250e-4 
            1.71661376953125e-4 
            8.58306884765625e-5 
            4.29153442382812e-5 
            2.14576721191406e-5 
            1.07288360595703e-5 
            5.36441802978516e-6
        </resolutions>
    </grid>
```

- `<srs>` ... `EPSG:4326 (WGS 84)`
- `<units>` ... `dd` = 度
- `<extent>` ... 西端=-180度, 南端=-90度, 東端=180度, 北端=90度

##### 2-3-3-2. 定義済みグリッド GoogleMapsCompatible

```xml
    <grid name="GoogleMapsCompatible">
        <metadata>
            <title>GoogleMapsCompatible</title>
            <WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible</WellKnownScaleSet>
        </metadata>
        <extent>-20037508.3427892480 -20037508.3427892480 20037508.3427892480 20037508.3427892480</extent>
        <srs>EPSG:3857</srs>
            <srsalias>EPSG:900913</srsalias>
        <units>m</units>
        <size>256 256</size>
        <resolutions>
            156543.0339280410
            78271.51696402048
            39135.75848201023 
            19567.87924100512
            9783.939620502561
            4891.969810251280
            2445.984905125640
            1222.992452562820
            611.4962262814100
            305.7481131407048
            152.8740565703525
            76.43702828517624
            38.21851414258813
            19.10925707129406
            9.554628535647032
            4.777314267823516
            2.388657133911758
            1.194328566955879
            0.5971642834779395
            0.2985821417389697
            0.1492910708694849
            0.0746455354347424
        </resolutions>
    </grid>
```

- `<srs>` ... `EPSG:3857 (WGS 84 Pseudo-Mercator)`
    - `<srcalias>` ... `ESPG:900913`
- `<units>` ... `m` = メートル
- `<extent>` ... 範囲

実のところ、先ほど定義した `ISG` グリッドは、これをコピーして `<extent>` と `<resolutions>` とに限定を加えたものです。

##### 2-3-3-3. 定義済みグリッド g

```xml
    <grid name="g">
        <metadata>
            <title>GoogleMapsCompatible</title>
            <WellKnownScaleSet>urn:ogc:def:wkss:OGC:1.0:GoogleMapsCompatible</WellKnownScaleSet>
        </metadata>
        <extent>-20037508.3427892480,-20037508.3427892480,20037508.3427892480,20037508.3427892480</extent>
        <srs>EPSG:900913</srs>
            <srsalias>EPSG:3857</srsalias>
        <size>256 256</size>
        <resolutions>
            156543.0339280410
            78271.51696402048
            39135.75848201023
            19567.87924100512
            9783.939620502561
            4891.969810251280
            2445.984905125640
            1222.992452562820
            611.4962262814100
            305.7481131407048
            152.8740565703525
            76.43702828517624
            38.21851414258813
            19.10925707129406
            9.554628535647032
            4.777314267823516
            2.388657133911758
            1.194328566955879
            0.5971642834779395
            0.2985821417389697
            0.1492910708694849
            0.0746455354347424
        </resolutions>
    </grid>
```

- `<srs>` ... `ESPG:900913`
    - `<srcalias>` ... `EPSG:3857 (WGS 84 Pseudo-Mercator)`
- `<units>` ... `m` = メートル
- `<extent>` ... 範囲

`<srs>` と `<srsalias>` が入れ替わっているだけですので、実質的には一つ前の `GoogleMapsCompatible` と同一のグリッドです。

##### 2-3-3-4. 定義済みグリッドのカスタマイズ

タイルセットの定義において、次の書式を使うと定義済みグリッドをカスタマイズすることが出来ます。

```xml
    <tileset name="foo">
        ...
        <grid restricted_extent="{xmin} {ymin} {xmax} {ymax}"
            tolerance="{tiles}"
            minzoom="{min}" maxzoom="{max}">{grid}</grid>
        ...
    </tileset>
```

- `restricted_extent` ... この範囲に限定
- `tolerance` ... 限定された範囲の上下左右に追加されるタイルの数
    - 省略時は `5`
- `minzoom` ... 最小ズームレベル
- `maxzoom` ... 最大ズームレベル

正確に言えば、グリッドそのものを修正するのではなく、範囲とズームレベルを限定して使用するわけです。

従って、`GoogleMapsCompatible` を真似して作成した `ISG` グリッドを使っている `cs` タイルセットの定義は、次のように書くことも出来ます。

```diff_xml
        <tileset name="cs">
            <source>cs</source>
            <cache>disk</cache>
-           <grid>ISG</grid>
+           <grid restricted_extent="15013800 4180200 15016600 4183700"
+               tolerance="0" minzoom="8">GoogleMapsCompatible</grid>
            <format>PNG</format>
            <metatile>5 5</metatile>
            <metabuffer>10</metabuffer>
        </tileset>
```

ただし、デモ画面で確認するときは、初期位置が `0 0 0 0` の原点になるため、随分とスクロールしないと地図画像が見えてきません。画面右下に経度緯度で座標値が表示されるので、それを見ながら、`135 35` あたりまで辛抱強くスクロールしてください。

実用上は問題なく動作します。

#### 2-3-4. spots と tanada を追加する

"isg" プロジェクトのレイヤ "spots" と "tanada" を追加します。

##### 2-3-4-1. spots のためのソースとタイルセットを追加する

```xml
    <source name="spots" type="wms">
        <getmap>
            <params>
                <VERSION>1.3.0</VERSION>
                <FORMAT>image/png</FORMAT>
                <LAYERS>spots</LAYERS>
                <TRANSPARENT>true</TRANSPARENT>
            </params>
        </getmap>
        <http>
            <url>http://gis-svr.vmware:8082/isg</url>
        </http>
    </source>

    <tileset name="spots">
        <source>spots</source>
        <cache>disk</cache>
        <grid minzoom="6">ISG</grid>
        <format>PNG</format>
        <metatile>5 5</metatile>
        <metabuffer>100</metabuffer>
    </tileset>
```

- `ISG` グリッドのズームレベルを限定して使用します
    - そんなにケチケチ最適化しなくても良いのに
- ラベルがタイル境界で切れないように `<metabuffer>` に大きめの数値を設定しています

##### 2-3-4-2. tanada のためのソースとタイルセットを追加する

```xml
    <source name="tanada" type="wms">
        <getmap>
            <params>
                <VERSION>1.3.0</VERSION>
                <FORMAT>image/png</FORMAT>
                <LAYERS>tanada</LAYERS>
                <TRANSPARENT>true</TRANSPARENT>
            </params>
        </getmap>
        <http>
            <url>http://gis-svr.vmware:8082/isg</url>
        </http>
    </source>

    <tileset name="tanada">
        <source>tanada</source>
        <cache>disk</cache>
        <grid restricted_extent="15015200 4180400 15016100 4182100"
            tolerance="0" minzoom="5">ISG</grid>
        <format>PNG</format>
        <metatile>5 5</metatile>
        <metabuffer>10</metabuffer>
    </tileset>
```

- `ISG` グリッドの範囲とズームレベルを限定して使用します
    - そんなにケチケチ最適化しなくても良いのに

## 3. QWC2 の設定

MapCache の準備が出来たので、QWC2 で利用してみます。

前回で説明した外部レイヤの仕組みを利用して、`"spots"`, `"tanada"`, `"cs"` のレイヤを MapCache によるレイヤ（＝タイルセット）に置換します。

### 3-1. 外部レイヤとして定義する

まず、`"themes"` ノードの下の外部レイヤの定義に MapCache によるレイヤを追加します。

```json
    "externalLayers": [
        {
            "name": "spots",
            "type": "wms",
            "url": "http://webgis.mydomain:8080/mapcache",
            "params": {
                "SERVICE": "WMS",
                "VERSION": "1.3.0",
                "TRANSPARENT": true,
                "FORMAT": "image/png",
                "LAYERS": "spots",
                "tiled": true
            },
            "featureInfoUrl": "http://webgis.mydomain:8082/isg",
            "legendUrl": "http://webgis.mydomain:8082/isg",
            "queryLayers": ["spots"],
            "infoFormats": ["text/xml"]
        },
        {
            "name": "tanada",
            "type": "wms",
            "url": "http://webgis.mydomain:8080/mapcache",
            "params": {
                "SERVICE": "WMS",
                "VERSION": "1.3.0",
                "TRANSPARENT": true,
                "FORMAT": "image/png",
                "LAYERS": "tanada",
                "tiled": true
            },
            "featureInfoUrl": "http://webgis.mydomain:8082/isg",
            "legendUrl": "http://webgis.mydomain:8082/isg",
            "queryLayers": ["tanada"],
            "infoFormats": ["text/xml"]
        },
        {
            "name": "cs",
            "type": "wms",
            "url": "http://webgis.mydomain:8080/mapcache",
            "params": {
                "SERVICE": "WMS",
                "VERSION": "1.3.0",
                "TRANSPARENT": true,
                "FORMAT": "image/png",
                "LAYERS": "cs",
                "tiled": true
            },
            "featureInfoUrl": "http://webgis.mydomain:8082/isg",
            "legendUrl": "http://webgis.mydomain:8082/isg",
            "queryLayers": ["cs"],
            "infoFormats": ["text/xml"]
        },
        ...
    }
```

- `"name"` ... 外部レイヤの識別名
- `"type"` ... 固定値 `"wms"`
- `"url"` ... `MapCache` の `URL`
- `"params"` ... `GetMap` リクエストのパラメータ
    - `"SERVICE"` ... 固定値 `"WMS"`
    - `"VERSION"`... 通常は `"1.3.0"`
    - `"TRANSPARENT"` ... 通常は `true`
    - `"FORMAT"` ... 通常は `"image/png"`
    - `"LAYERS"` ... レイヤ識別名（タイルセット識別名）
    - `"tiled"`... 固定値 `true`
- `"featureInfoUrl"`... 地物情報を取得するための `URL`
`QGIS Server` の `URL` を指定する
- `"legendUrl"`... 凡例情報を取得するための `URL`
`QGIS Server` の `URL` を指定する
- `"queryLayers"`... `QGIS Server` でのレイヤ識別名
- `"infoFormats"`... 固定値 `"text/xml"`

地物情報と凡例情報のデータは `MapCache` には存在しません。そのため、`GetFeatureInfo` リクエストと `GetLegend` リクエストは直接 `QGIS Server` に対して送ります。そのための設定が最後の4項目です。

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

次に、テーマごとの `"items"` ノードの下の外部レイヤの定義に、内部レイヤと外部レイヤの関連付けを追加します。

```json
    "items": [
        {
            ...
            "externalLayers": [
                {
                    "name": "spots",
                    "internalLayer": "spots"
                },
                {
                    "name": "tanada",
                    "internalLayer": "tanada"
                },
                {
                    "name": "cs",
                    "internalLayer": "cs"
                },
                ...
            ],
            ...
        }
    ],
    ...
```

- `"name"` ... 外部レイヤ識別名
- `"internalLayer"` ... 内部レイヤ識別名


## 4. Seeding （種蒔き）

`mapcache_seed` コマンドを使うと、キャッシュ・データをあらかじめ全部タイルにセットしておくことが出来ます。

```bash
sudo -u apache mapcache_seed -c /etc/mapcache.xml -t spots
sudo -u apache mapcache_seed -c /etc/mapcache.xml -t tanada
sudo -u apache mapcache_seed -c /etc/mapcache.xml -t cs
```

- `sudo -u apache` ... `apache` にスイッチして実行
- `-c /etc/mapcache.xml` ... 設定ファイルを指定
- `-t` ... タイルセットを指定

`mapcache_seed` コマンドの詳細については、`mapcache_seed -h` で確認してください。

重要だと思われるオプションのみを解説します

- `-c|--config [value]` : 設定ファイルの指定（必須）
- `-t|--tileset [value]` : タイルセットの指定（必須）
- `-e|--extent [value]` : 範囲の指定 format: `minx,miny,maxx,maxy`
- `-f|--force` : 強制的に再作成
- `-g|--grid [value]` : グリッドの指定
- `-h|--help` : ヘルプを表示
- `-m|--mode [value]` : 動作モード : `seed` (デフォルト), `delete` or `transfer`
- `-M|--metasize [value]` : seeding 中の `metatile` サイズを上書き指定、例えば `8,8`
- `-n|--nthreads [value]` : 使用する並行スレッド数を指定（`-p` と非互換）
- `-o|--older [value]` : 指定した日時より古いタイルを再作成 (format: `year/month/day hour:minute`, eg: `2011/01/31 20:45`)
- `-p|--nprocesses [value] ` : 使用する並行プロセス数を指定（`-n` と非互換）
- `-P|--percent [value]` : 最近 1000 回でエラーがこの数値を超えたら中止。デフォルトは `1%`。`0` を指定するとエラー発生と同時に中止
- `-q|--quiet` : 進捗情報を非表示
- `-v|--verbose` : デバッグ・ログ・メッセージを表示
- `-b|--non-interactive` : 進捗情報を新しい行に表示
- `-x|--transfer [value]` : 転送するタイルセット
- `-z|--zoom [value] ` : 作成するズームレベルの最小と最大、例えば `0,6`

## And so, what's next?

これで QGIS Server + QWC2 + MapCache の連携が完了しました。

まだ、いくつか MapCache に対応していないレイヤが残っていますが、これまでに説明した手順で作業をすれば、すべて対応させることが出来ます。

次は、QWC2 に戻って、ユーザ・インタフェースや機能のカスタマイズを行います。おそらく、一回では完了しないと思います。

- [QGIS データを Web で表示 #7 - QWC2（続々）](https://qiita.com/softark/items/c0e7bc72a07ad2aa85ac)