QGISでマップキャンバス中央に近いジオタグ付写真を動的に表示するスタイルを作成した

# はじめに
いまは公開停止となっているiOSアプリfeildaceess風に、写真を上部に並べて、写真と写真撮影位置と線で結んで表示するQGISスタイルとモデルを作ったので紹介します。
feildaccess2（iOSアプリ）のスクリーンショット（ちなみに場所は愛媛県の滑床渓谷です）
![00feildaccess.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/8e914795-9f18-545b-40bd-4ab8f894d92d.png)

# QGISスタイル、モデルファイルの置き場所
https://github.com/motohirooya/photoview

# 使い方の流れ
## ジオタグ付き写真の写真を用意する
GPS付カメラで写真を撮るなどします
　
## モデルの読み込み
QGISを起動し、プロセッシングツールボックスの歯車のアイコンから「ツールボックスにモデル」を追加を選択し、上記に置いている2つの.model3ファイルを指定します。
そのあと、PhotoView_Inport を右クリック→「出力のためのレンダリングスタイルを編集」→「PhotoView.qml」を設定します。
![01ツールボックスにモデルを追加.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/7f26ec00-603c-eda4-ada3-18ba3043e4f5.png)
![02追加.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/4f5aed0b-c47e-948d-888c-0629c67a4d82.png)


## プロジェクトのCRSに投影座標系（平面直角座標）を設定
メニューのプロジェクト→プロパティ→座標参照系　を開き、写真を撮影した地域の投影座標系（平面直角座標）を設定します。
https://www.gsi.go.jp/sokuchikijun/jpc.html
残念ながら、地理座標系では動きません。また、地図を回転すると動きません。


## ジオタグ付き写真からレイヤを作成
プロセッシングツールボックス→「PhotoView_Inport」を開き、Folderにジオタグ付き写真を保存したフォルダを指定し、実行します。
マップキャンバスの上部に写真が表示されます。
![03写真表示.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/4e007083-5c91-fe96-0e40-34e22f7d41d4.png)
このモデルは、下記を行い、ポイントレイヤを出力します。
1. プロセッシング「ジオタグ（位置情報）付きの写真」
1. プロセッシング「レイヤの再投影」。上記をプロジェクトCRS（投影座標が期待される）に再投影
1. プロセッシング「属性のリファクタリング」2つのフィールド追加
   - invisible(真偽値）　真の場合、写真の表示対象から除外
   - label（STRING)　値が入っている場合、ラベルとして使用。ない場合filenameフィールドを使用

## 表示のパラメータの設定・更新
「PhotoView_Parameter」のモデルを使ってプロジェクト変数を設定・更新すると、写真の枚数や余白などのパラメータをを変えることができます。以下は写真枚数を変更した例
![04写真枚数変更.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/8621eb43-02f0-4490-c6ee-f6824fc3a70f.png)


## プリントコンポーザ
プリントコンポーザでも同様に表示されます。
![06print.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/5f3129cf-c798-7027-3e11-8675188fb8fb.png)

# 内部の処理について
## 座標の計算など
写真はラスター画像マーカで、写真の上中央をポイントジオメトリで指定しており、この位置の計算がこのスタイルのキモになります。
画面中央から近い画像を表示することを実現するには、他のポイントとの比較をする必要があり、表示範囲のポイントを抽出して、画面中央との距離の配列を作って並べ替えた後、指定数を抽出し、さらにX座標で並べ替えて位置決めをしています。
以下は、ラスター画像マーカのアンカー位置を計算する式です。

```
/*
ラスタ画像マーカーのアンカー位置、ラベルのアンカー位置　共通
*/
with_variable('Number',  to_int( coalesce(@PhotoView_Number , 5) ) ,
with_variable('TopMargin', to_real(coalesce(@PhotoView_TopMargin ,8.0))*@map_scale/1000,
with_variable('PhotoBySideMargin',to_real(coalesce(@PhotoView_PhotoBySideMargin , 15.0)),
with_variable('SideMargin',	@map_extent_width /(1+(@PhotoBySideMargin+1)*@Number),
with_variable('Extent',intersection(  @map_extent , translate(  @map_extent ,0,-@PhotoBySideMargin*@SideMargin*0.800-@TopMargin)),
with_variable('i',  --左から何番目か
 array_find(
  array_foreach(
    array_sort(
	 array_foreach( 
  	  array_slice(
       array_sort( 
        array_agg(  array(  distance( @map_extent_center , $geometry ),$id ,$x ) ,filter:=if( attribute(  'invisible' ),false,true) and within(   $geometry , @Extent ))
        )
      ,0,@Number-1)
	, array_reverse(@element))
    )
  ,@element[1])
 ,$id),		-- i の定義終わり
 with_variable('p0',translate(@map_extent_center,(1+@PhotoBySideMargin/2)*@SideMargin-@map_extent_width/2,
  @map_extent_height/2-@TopMargin),
 translate(@p0,(1+@PhotoBySideMargin)*@SideMargin*@i,0)
)))))))
```

下図は、座標計算のメモです。

![07座標.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/55dd559f-1b23-6538-6520-2cbcd895042a.png)

with_variable()は関数内で使えるローカル変数を定義できる関数で、これなしではこれは作れません。
```
構文 with_variable(name,value,expression)
引数
name　設定する変数の名前
value　設定する値
expression　変数が使用できる式
```

## 曲線の引き出し線
参考にしたfeildaceessと違い、写真の引き出し線は曲線になっています。
これは、写真の縦横比が取得できないため、写真の下端が設定できず、直線だと適切な位置に写真側の端点を設定できなかったため、写真からいったん下げて、写真と結んだラインをスムージングするという処理にしたためです。

## 処理速度
個別の地物ごとに表示範囲の地物のジオメトリの配列化→画面中心との距離計算→並べ替えを行っているので、表示範囲の地物の数が増えると極端に重くなります。下記は地物が500個の例で、ノートPCのi7で12秒かかりました。100個くらいなら、そんなに処理は重くありません。
![05many.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2259894/80e6afdd-88ce-8c2b-7fb9-e91d2c9d21c1.png)


# おわりに
旅行で写真を撮ったら、試しに使ってみてください。