qwc-services による Web GIS の構築 #5 編集機能

QGIS Server と QGIS Web Client 2 (QWC2) を中核とする統合的 Web GIS エコ・システムである qwc-services によって Web GIS を構築する作業記録の第5回です。

第1回では、`qwc-docker` によって `qwc-services` をインストールし、`MapCache` とも連携させて、QGIS のプロジェクトを Web 地図として表示するところまでやりました。

- [qwc-services による Web GIS の構築 #1 qwc-docker](https://qiita.com/softark/items/bb179315d404fdd3cf9f)

第2回では、ユーザの認証と権限管理について調べて、地図やレイヤを閲覧できるユーザを限定したり、ビューワの機能を使用できるユーザを限定したりする方法を確認しました。

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

第3回では、qwc-services によって実現される「編集」関連の機能に不可欠なデータベース環境である `PostGIS` を使えるようにしました。

- [qwc-services による Web GIS の構築 #3 PostGIS](https://qiita.com/softark/items/74861df6a6ebfd359eaa)

第4回は、ちょっと寄り道をして、`QWC2 Viewer` とは独立したページを `Web GIS` に追加する方法として、`Python` のマイクロ・ウェブ・フレームワークである `Flask` の使用を検討しました。

- [qwc-services による Web GIS の構築 #4 独自ページを追加](https://qiita.com/softark/items/410fca12a8f5ad8ee129)

今回は、いよいよ `qwc-services` と `PostGIS` によって実現される編集機能を見ていきます。

## 1. 編集機能を利用するための条件

マニュアル（[QWC2 > Selected topics > Editing > Quick Start](https://qwc-services.github.io/master/topics/Editing/#quick-start)
）によると、`QWC2` ビューワの編集機能を利用してレイヤを編集できるようにするためには、次に示すステップを踏んでくれ、とのことです。

1. レイヤのデータソースを `PostGIS` データベースにする
    - 特にデータセットのプライマリ・キーを必ず構成する
2. `QGIS` の `PostgreSQL` 接続をサービス名を使って構成する
    - そのサービスをホストの `pg_service.conf` と `qwc-docker/pg_service-write.conf` に追加する
    - データベース・ホストが `Docker` コンテナ内からアクセス可能であることを必ず確認する
3. プライマリ・キー・フィールドが `serial` タイプである場合は、`QGIS` の属性フォームで対応するフィールド・ウィジェット・タイプを `非表示` にする
4. `QWC Admin` で対応する `Map` リソースの子として `Data` リソースを作成する
    - 編集を許可するロールに、`Map` と `Data` の両方に対する許可を与える
5. `Data` リソースに対する許可において `Write` チェックボックスを `OFF` のままにすると、データセットは `Read Only` としてアクセスを許可される
   - 属性テーブルや地物フォームをデータセットの表示だけに利用し、編集を許可したくない場合に有効
6. `QWC Admin` で `Generate service configuration` ボタンを押して、サービス構成を更新する
 
以下、詳しく見ていきます。

### 1-1. PostGIS は必須

`QWC2` ビューワの編集機能は、`QGIS Server` の `WMS` や `WFS` のサービスを利用するのではなく、レイヤのデータソースとなっている `PostGIS` のテーブルに直接アクセスして情報を取得したり更新したりする仕様になっています。つまり `Shapefile` をデータソースとするレイヤは編集機能の対象外なのです。

従って、前々回で説明したように、編集対象とするベクタ・レイヤは `Shapefile` から `PostGIS` テーブルにデータを移行する必要があります。

- [qwc-services による Web GIS の構築 #3 PostGIS](https://qiita.com/softark/items/74861df6a6ebfd359eaa)
    - [1-5-3. データベース接続のテスト（データのエクスポート）](https://qiita.com/softark/items/74861df6a6ebfd359eaa#1-5-3-%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E6%8E%A5%E7%B6%9A%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88)
    - [2-3-3. 一括インポートのスクリプト](https://qiita.com/softark/items/74861df6a6ebfd359eaa#1-5-3-%E3%83%87%E3%83%BC%E3%82%BF%E3%83%99%E3%83%BC%E3%82%B9%E6%8E%A5%E7%B6%9A%E3%81%AE%E3%83%86%E3%82%B9%E3%83%88%E3%83%87%E3%83%BC%E3%82%BF%E3%81%AE%E3%82%A8%E3%82%AF%E3%82%B9%E3%83%9D%E3%83%BC%E3%83%88)

どのような方法でも構いませんが、上で述べられているように、プライマリ・キーを必ず設定する必要があります。`pgAdmin4` などでテーブルを確認して、プライマリ・キーが適切に設定されていない場合は手作業で修正して下さい。

### 1-2. pg_service.conf を使う

同じく前々回で説明したように、データベースへの接続には `pg_service.conf` を使用します。

- [qwc-services による Web GIS の構築 #3 PostGIS](https://qiita.com/softark/items/74861df6a6ebfd359eaa)
    - [1-5-1. Windows に pg_service.conf を設置する](https://qiita.com/softark/items/74861df6a6ebfd359eaa#1-5-1-windows-%E3%81%AB-pg_serviceconf-%E3%82%92%E8%A8%AD%E7%BD%AE%E3%81%99%E3%82%8B)
    - [4-1. qwc-services の pg_service.conf を更新する](https://qiita.com/softark/items/74861df6a6ebfd359eaa#4-1-qwc-services-%E3%81%AE-pg_serviceconf-%E3%82%92%E6%9B%B4%E6%96%B0%E3%81%99%E3%82%8B)
    - [4-2. pg_service.conf を使うメリット](https://qiita.com/softark/items/74861df6a6ebfd359eaa#4-2-pg_serviceconf-%E3%82%92%E4%BD%BF%E3%81%86%E3%83%A1%E3%83%AA%E3%83%83%E3%83%88)

> そのサービスをホストの `pg_service.conf` と `qwc-docker/pg_service-write.conf` に追加する

この部分はドキュメントが若干説明不足です。

通常は `qwc-docker/pg_service-write.conf` を使用する必要はなく、`qwc-docker/pg_service.conf` に当該サービスを追加登録するだけで構いません。

`qwc-docker/pg_service-write.conf` は、`qwc-data-service` において `qwc-docker/pg_service.conf` の代りに使用することが出来ます。

```diff_yaml:docker-compose.yml
   qwc-data-service:
     image: sourcepole/qwc-data-service:latest-2025-lts
     environment:
       <<: *qwc-service-variables
       SERVICE_MOUNTPOINT: '/api/v1/data'
       ATTACHMENTS_BASE_DIR: '/attachments'
       MAX_ATTACHMENT_FILE_SIZE: 1048576
     volumes:
       - ./volumes/config:/srv/qwc_service/config:ro
       - ./volumes/attachments:/attachments
-      - ./pg_service.conf:/srv/pg_service.conf:ro
+      # - ./pg_service.conf:/srv/pg_service.conf:ro
       # NOTE: you can mount a pg_service-write.conf with R/W DB users and have R/O DB users in the main pg_service.c    onf
-      # - ./pg_service-write.conf:/srv/pg_service.conf:ro
+      - ./pg_service-write.conf:/srv/pg_service.conf:ro
```

つまり、他のサービスには `qwc-docker/pg_service.conf` を読ませるけれども、`qwc-data-service` だけには `qwc-docker/pg_service-write.conf` を代りに読ませるという設定が可能です。これによって、`PostGIS` に対する `r/w` 権限を持ったユーザを使う設定を `qwc-data-service` に限定することが可能になります。

`qwc-data-service` が二つの設定を読んで使い分けてくれるという事ではありません。

### 1-3. serial 型のプライマリ・キーは非表示にする

`QGIS Desktop` で、レイヤのプロパティを開き、属性フォームで `serial` 型のプライマリ・キーのウィジェット型を「非表示」にします。

ウィジェット型を「非表示」にせず、代りに「編集可能」のチェックボックスを `OFF` にしても良いでしょう。

### 1-4. Map リソースと Data リソースを作成して許可を与える

#### 1-4-1. リソースの作成

`QWC Admin` で `Map` リソースと `Data` リソースを作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/adb5a95c-b49f-43b5-a41d-5dd79cba5419.png)

- `Map` リソース ... 編集可能なレイヤの親である地図のリソース
- `Data` リソース ... 編集可能なレイヤを表わすリソース
    - `Layer` リソースは `WMS` 経由でアクセス可能なレイヤを表わすのに対して、`Data` リソースは `PostGIS` 経由でアクセス可能なレイヤを表わす

`[＋ New Resource]` ボタンからリソースを追加しても構わないのですが、`[Imoort maps]`、`[Import resources]` ボタンを使って自動的に登録することが可能です。自動登録の方がスペルミスや親子関係の間違いが無いので確実です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/db32a50c-9610-4c1f-8d7a-9b2b1d41a08a.png)

上の図は、`Map` リソースの `[Import resources]` ボタンを押したときに表示される入力フォームです。

- リソース ... `Map` リソースを親とする子リソースのタイプを指定
    - 通常は `Data` を選択
    - `Data(create)`, `Data(read)`, `Data(update)`, `Data(delete)` は、データ・リソースへのアクセスを細かく制御したい場合に使用
- ロール ... 自動登録するリソースに対する権限を付与するロールを指定
- `Write` チェックボックス
    - `ON` ... R/W 許可
    - `OFF` ... Read Only 許可

`[Import resources]` ボタンを押すと `Map` に含まれる全てのレイヤについて、子のリソースが自動作成されます。そして同時に指定したロールに許可が与えられます。

結果的に不要なリソースも作成されますので、要らないものは後で削除して下さい。

#### 1-4-2. リソースに対する許可を与える

許可（`Permissions`）の一覧表示で確認して、レイヤの編集を許可したいロールに対して `Data` リソースと親の `Map` リソースの許可を付与します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/507efb01-4dcc-4e13-b124-97ac9f0f2333.png)

- リソース ... ドロップダウンから選択
- ロール ... リソースに対する権限を付与するロールを指定
- `Write` チェックボックス
    - `ON` ... R/W 許可
    - `OFF` ... Read Only 許可

:::note warn
あるロール対してに `Data` リソースへのアクセスを許可する場合は、そのロールに対して `Data` リソースの親である `Map` リソースへのアクセス許可も同時に付与する必要があります。

例えば、あるユーザが `user` ロールと `editor` ロールの両方を持っており、`user` ロールに対して `Map` へのアクセス許可、`editor` ロールに対して `Data` へのアクセス許可が与えられていれば、理屈の上ではそのユーザがレイヤを編集して構わない筈です。しかし `QWC2` ビューワではこのユーザはレイヤを編集することが出来ません。`editor` ロールに `Map` へのアクセス許可も追加する必要があります。

`Data` へのアクセス許可の有無は、ユーザのレベルではなく、ロールのレベルで判定されるためです。
:::

### 1-5. 構成の再作成

リソースや許可を変更したときは、必ず `[Generate service configuration]` ボタンを押して構成を再作成する必要があります。

## 2. レイヤの属性フォームの設定

`QWC2` ビューワの編集機能は `QGIS` プロジェクトにおけるレイヤの属性フォームの設定を参照します。

「レイヤ > プロパティ > 属性フォーム」を開いて、各フィールドについて設定を行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/1ee3f1da-8d05-4830-9520-489edc4d67b5.png)

### 2-1. 別名（Alias）

「別名（Alias）」に設定した値が地物フォームや属性テーブルにおいてフィールド名の代りに使用されます。

### 2-2. 編集可能

「編集可能」のチェック・ボックスを `OFF` に設定すると、地物フォームや属性テーブルにおいて、フィールドは `Read Only` の扱いになります。

### 2-3. ウィジェット型

主なウィジェット型としては、

- 非表示
- テキスト編集
- 日付/時刻
- 範囲（Range）
- チェックボックス
- バリューマップ（＝ドロップダウン・リスト）
 
があります。

#### 2-3-1. 非表示

フィールドを非表示とするためのウィジェット型です。

`serial` 型のプライマリ・キーなど、ユーザに編集を許さず、見せる必要すら無いフィールドに適しています。

#### 2-3-2. テキスト編集

一般的なテキスト入力欄を表示するウィジェットです。

文字列のフィールドも数値のフィールドも、デフォルトではこの型のウィジェットを使います。

#### 2-3-3. 日付/時刻

日付、時刻、日付/時刻型のフィールドに用いられるウィジェットです。

#### 2-3-4. 範囲（Range）

数値型のフィールドに使用できるウィジェットで、以下を設定することが出来ます。

- 下限値
- 上限値
- 小数点以下の桁数
- タイプ
    - テキスト & スピンボタン
    - スライダー
    - ダイアル（`QWC2` ビューワは未対応）
- ステップ

:::note
数値型のフィールドにはデフォルトでは「テキスト編集」ウィジェットが割り当てられますが、この「範囲（Range）」ウィジェットの使用を検討して下さい。

- 下限値、上限値を指定出来る
- 実数の場合、小数点以下の桁数を指定できる
- スピンボタンやスライダを使用できる

というメリットがあります。
:::

:::note warn
「範囲（Range）」ウィジェットを使用すると

- スピンボタンやスライダを使用できる

というメリットがありますが、反面、不要な場合でもスピンボタンを非表示に出来ないというデメリットもあります。
:::

#### 2-3-5. チェックボックス

`bool`, `boolean` 型のフィールドで使うことを想定したウィジェットです。

#### 2-3-6. バリューマップ（＝ドロップダウン・リスト）

ドロップダウン・リストから値を選択したい場合に使用できるウィジェットです。文字列型のフィールドにも、数値型のフィールドにも使用することが出来ます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/9b1a92ee-c62a-43b9-b5b1-5102c543d16c.png)

- 値 ... フィールドに入力される値
- 説明 ... ドロップダウン・リストに表示される文字列

リストの作成には「レイヤからデータをロード」を利用することが出来ます。

## 3. 編集対象レイヤでキャッシュを使用しない

編集機能を使う場合は、地物データと地図画像キャッシュとの間に生じるズレが問題になってきます。

つまり、地物データが更新されたのに、ビューワが古い地図画像キャッシュを表示するために、次のような不具合が生じます。

- 追加した地物が表示されない
- 削除した地物がまだ表示されている
- 位置や形状を変更した地物が古い位置や形状で表示される
- 変更した文字ラベルが古いまま表示される

不具合の回避措置として、編集対象となるレイヤでキャッシュを使わないように設定を変更します。

具体的には、`qwc-docker/volumes/config-in/default/themesConfig.json` で内部レイヤと外部レイヤの関係を定義しているブロックにおいて、編集対象のレイヤの分を削除します。

```diff_json:themesConfig.json
        "themes": {
            "items": [
                {
                    "id": "isg",
                    "name": "isg",
                    "url": "/ows/scan/isg",
                    "externalLayers": [
-                       {
-                           "name": "spots",
-                           "internalLayer": "spots"
-                       },
-                       {
-                           "name": "tanada",
-                           "internalLayer": "tanada"
-                       },
-                       {
-                           "name": "bld",
-                           "internalLayer": "bld"
-                       },
                        {
                            "name": "road",
                            "internalLayer": "road"
                        },
```

これで編集対象のレイヤはキャッシュを介さずに表示されるようになります。

:::note warn
これは、パフォーマンスを犠牲にした一時的な回避措置です。

パフォーマンスを犠牲にせずに編集機能と地図画像キャッシュとを両立させる方法については、次回に説明しますので、参照して下さい。
:::

## 4. ビューワの編集機能

### 4-1. 地物フォーム

メニューやトップバーから「地物フォーム」を選択すると、「地物を確認したい地点をクリック」というプロンプトが表示されます。

編集可能な地物をクリックすると、下の図のような「地物フォーム」パネルが開いて、選択された地物の属性とジオメトリを編集することが可能になります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/d91a88c6-8036-49eb-a2a1-765c5ccfa40a.png)

- `Data` または `Dara (read)` 権限のみの場合は閲覧のみ可能
- 属性の編集
    - 権限が必要
        - `Data (write)` または `Data (update)` 権限
    - 数値や文字列の属性をフォームで編集することができる
    - 編集を完了させたければ「コミット」
    - 編集を中止したければ「中止」
- ジオメトリの編集
    - 権限が必要
        - `Data (write)` または `Data (update)` 権限
    - 選択された地物の制御点が表示される
    - 制御点を掴んで移動することができる
    - ポリゴンやポリラインは、ライン上でクリックすると、新しい制御点を追加できる
    - 制御点の上で `Shift + クリック` で削除
    - 編集を完了させたければ「コミット」
    - 編集を中止したければ「中止」
- 削除
    - 権限が必要
        - `Data (write)` または `Data (delete)` 権限
    - 地物そのものを削除することができる
    - 編集を開始する前または完了した後のみ「削除」ボタンが出る

### 4-2. 属性テーブル

メニューやトップバーから「属性テーブル」を選択すると、下の図のような「属性テーブル」が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/2dc1b4d8-358f-455a-bd50-421b4e0f90ce.png)

#### 4-2-1. 一覧表示

レイヤを選択して「再ロード」ボタンを押すと、下の図のように、レイヤの地物が一覧表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/e9198b72-0120-4bfd-93d8-0aaa23b64a52.png)

- ヘッダ
    - クリックすると、その属性の値で表をソート
- フッタ
    - ページングボタン
    - ページあたりの行数を選択するドロップダウン
    - フィルタ
        - 属性
        - 演算子
            - `~` ... 部分一致
            - `=` ... 一致
            - `>` ... 大きい
            - `>=` ... 等しいか大きい
            - `<=` ... 等しいか小さい
            - `<` ... 小さい
        - 比較対象値

下の図では、「利用状況｣に｢コシヒカリ｣という文字列を含むものを面積の小さい順にリストしています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/ff41cc89-068e-4014-8ad1-7959962eba06.png)

#### 4-2-2. マップ・キャンバス上での位置表示

下の図のように、属性テーブル上の行にマウスをホバーさせると、その地物がマップ・キャンバス上で示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/071310eb-b303-463e-945a-fba654bae4e3.png)

#### 4-2-3. ツールボタン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/fe3cf43f-c75c-43a4-8abc-22acce0083c9.png)

| ボタン | 機能 | 内容 |
|:-:|:--|:--|
|![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/ac9b7707-0b7b-4982-bb04-2d4168b24c07.png) | 再ロード | レイヤを変更したときなどに一覧表を再ロード |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/0160b7d3-9c52-49ea-a860-757374f63270.png) | 地物を追加 | 属性テーブルではできないので「編集フォームを使ってこのレイヤに新しい地物を追加して下さい」というメッセージが表示される（だけ） |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/aa4e3cb2-cc93-48c3-b153-ec3f2af2e236.png) | 地物にズーム | チェックボックスで選択された地物（複数可）にズーム |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/b1362b75-6cd0-4aff-989d-9c822afa5078.png) | フォームで編集 | チェックボックスで選択された地物（一つだけ）を地物フォームで編集 |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/bf9ac2ba-eadf-49ef-925e-518f753001b5.png) | 削除 | チェックボックスで選択された地物（複数可）を削除 |
| ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/b88af100-f3d7-4ac5-a20c-ec8c37412b1f.png) | CSV エクスポート | テーブルの内容を CSV にエクスポート |

地物を削除するためには、`Data (write)` または `Data (delete)` の権限が必要です。

#### 4-2-4. 属性のインライン編集

`Data (write)` または `Data (update)` の権限がある場合は、テーブルのセルで直接に属性を編集することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/19bed4d5-5ed5-4c5e-bed0-e4f282d0eecb.png)

インライン編集を開始すると、その他の行は `disabled` の状態になり、その行でのみ編集を続けることができます。

他の行を編集したい場合は、「変更された行をコミット」するか、「変更された行を元に戻す」かしなくてはなりません。

### 4-3. 編集

メニューやトップバーから「編集」を選択すると、下の図のような「編集」パネルが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/7f8a64df-d9c9-4fc2-9f6e-c3bea1699dc5.png)

#### 4-3-1. 選択

「選択」ボタンを押すと下の図のように「地物フォーム」とほとんど同じ内容のパネルが開かれます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/d584d6cd-0ee1-4073-9945-be7ddf5a2ce0.png)

機能も「地物フォーム」と同じです。ただし、あらかじめ選んだレイヤ以外の地物をクリックしても、反応はありません。

#### 4-3-2. 作図

「作図」ボタンを押すと、選択したレイヤに新しい地物を追加することができます。

追加できる種類の地物はレイヤによって異なります。

最初に、地物のジオメトリを仮決定します。

- 点
    - クリックで位置を仮決定
- ポリライン
    - クリックで始点を仮決定
    - クリックで経過点を仮決定
    - ...
    - ダブルクリックで終点を仮決定
- ポリゴン
    - クリックで頂点を仮決定
    - クリックで頂点を仮決定
    - ...
    - ダブルクリックで最後の頂点を仮決定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/f13fcf7e-2d82-497f-b111-9dc6d1f78e26.png)

上の図は、「建物」レイヤに新しいポリゴンを追加するために、4つめの頂点を仮決定しようとしているところです。

ジオメトリの仮決定が済むと、下の図のように地物フォームが表示されて、属性フィールドに値を入力することができるようになります。

この段階で仮決定したジオメトリを修正することもできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12310/5cffa670-3a03-4ac6-b534-b862494e3be7.png)

操作方法は「地物フォーム」と一緒です。

「コミット」または「中止｣して作図を終了します。

:::note
最初の段階で「既存のジオメトリをクローンして作図」することができます。

他のレイヤから地物をコピーしたい場合に使うと便利でしょう。
:::

#### 4-3-3. テーブル

すでに説明した「属性テーブル」を開くためのボタンです。

## And so, what's next?

すでに説明したように、編集機能が使えるようになると、地物データと地図画像キャッシュとの間に生じるズレが問題になってきます。

今回は一時的な対策として編集対象となるレイヤをキャッシュの対象外に設定したのですが、編集機能を阻害することなく地図画像キャッシュによるパフォーマンスも維持することが可能です。

次回はこの問題を取り扱います。

- [qwc-services による Web GIS の構築 #6 MapCache 再び](https://qiita.com/softark/items/0b779bdaa37e62a37c82)