SymbolタイプのMaputnikでの表示

# はじめに
[こちらの記事](https://qiita.com/k96mz/items/1ac756935b685d20dd7c)では、Maputnikでのスタイルの編集について記載しましたが、Symbolについては記載していませんでしたので、本記事で書こうと思います。また、spriteについて、どのような挙動になるのか、Maputnikで試してみます。
レポジトリは[こちら](https://github.com/k96mz/20240830symbol)です。

# データの準備
[Natural Earth](https://www.naturalearthdata.com/) のデータを使用します。ダウンロードページから、「1:110m Physical Vectors」の「Coastline」と「Land」、「1:110m Cultural Vectors」の「Admin 0 – Boundary Lines」と「Populated Places」をダウンロードします。
さらに、QGIS上に自身でポイントデータを以下の通り作成しました。

| id | name | location |
|:-:|:-:|:-:|
| 1 | UNGIS  | NewYork |
| 2  | UNGSC  | Brindisi |
| 3  | United Nations Support Base  | Valencia |

ダウンロードしたshpファイルをQGISで表示させてみると以下のようになります。

![スクリーンショット 2024-08-30 12.14.04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/9455d544-d7d9-9e25-f3b9-306722c1120d.png)

## ベクトルタイルの構成
以下のようなベクトルタイル構成とします。後にスタイル付けをする際に必要となってくるので、この時点で整理しておきます。全てのレイヤで、Min Zoomは0、 Max Zoomは3とします。


| データ | shp名 | GeoJSON名 | VT layer名 | Properties |
|:-:|:-:|:-:|:-:|:-:|
| Coastline | ne_110m_coastline.shp  | coastl.geojson | coastl |  |
| Land  | ne_110m_land.shp  | landmass.geojson | landmass |  |
| Admin 0 – Boundary Lines  | ne_110m_admin_0_boundary_lines_land.shp  | bndl.geojson | bndl |  |
| Populated Places | ne_110m_populated_places.shp  | popp.geojson | popp | NAME |
| Location | location.shp  | loc.geojson | loc | name, location |


# shpファイルからGeoJSONファイルへの変換
GDALを使用して変換します。shpファイルが保存されているフォルダに移動して、以下のコードを実行します。

```
ogr2ogr -f GeoJSON coastl.geojson ne_110m_coastline.shp
ogr2ogr -f GeoJSON landmass.geojson ne_110m_land.shp
ogr2ogr -f GeoJSON bndl.geojson ne_110m_admin_0_boundary_lines_land.shp
ogr2ogr -f GeoJSON popp.geojson ne_110m_populated_places.shp
ogr2ogr -f GeoJSON loc.geojson location.shp
```

# GeoJSONファイルからベクトルタイルへの変換
作成したGeoJSONファイルが存在するフォルダに移動し、tippecanoeを使用して以下のコードを実行します。

```
tippecanoe -e VTpractice --no-tile-compression --no-feature-limit --no-tile-size-limit --drop-rate=1 -Z0 -z3 -L coastl:coastl.geojson -L landmass:landmass.geojson -L bndl:bndl.geojson  -L popp:popp.geojson -L loc:loc.geojson
```

以下が作成したベクトルタイルの配信URLです。
https\://k96mz.github.io/20240830symbol/Data/VTpractice/{z}/{x}/{y}.pbf

# Maputnikでのスタイル設定
[Maputnik](https://maputnik.github.io/)を開いたら、「Data Sources」 > 「Add New Srouce」に以下の通り入力してベクトルタイルを読み込みます。

| プロパティ  | 値 | 説明 |
|:-:|:-:|:-:|
| Source ID  | v | 任意の値  |
| Source Type  | Vector(XYZ URLs)  | 既定値  |
| Tile URL | https\://k96mz.github.io/20240830symbol/Data/VTpractice/{z}/{x}/{y}.pbf | ベクトルタイルのURL |
| Min Zoom | 0 | 最小ズーム値  |
| Max Zoom | 3 | 最大ズーム値  |


## SpriteとGlyphsの設定
「Style Setting」で「Sprite URL」と「Glyphs URL」を以下の通り設定します。

Sprite URL: https://k96mz.github.io/20240830symbol/sprite/sprite
Glyphs URL: https://k96mz.github.io/20240830symbol/font/{fontstack}/{range}.pbf


以下が元にあったデータ場所です。コピーさせていただきました。
Sprite URL: https://ubukawa.github.io/VT-test-0614/sprite/sprite
Glyphs URL: https://ubukawa.github.io/VT-test-0614/font/{fontstack}/{range}.pbf

上記データがどのように作られたか分かっていませんが、とりあえずこちらを使用させていただきます。

:::note info
上記URLをMaputnikに貼り付けた時に、不要なスペースなどが入っていると、うまくspriteを反映出来ないので気をつけてください。
:::

spriteのjsonファイルは以下です。
https://k96mz.github.io/20240830symbol/sprite/sprite.json

spriteのpngファイルは以下です。
https://k96mz.github.io/20240830symbol/sprite/sprite.png

spriteについては、[こちらの記事](https://qiita.com/k96mz/items/b2ddc3df7d4000f52c39)をご覧ください。

## スタイルファイルの設定
次にスタイルファイルを作成します。「Add Layer」から以下の通り入力します。

◯Coastline
ID: coastl
Type: Line
Source: v
Source Layer: coastl

◯Land
ID: landmass
Type: Fill
Source: v
Source Layer: landmass

◯Admin 0 – Boundary Lines	
ID: bndl
Type: Line
Source: v
Source Layer: bndl

◯Populated Places
ID: popp
Type: Circle
Source: v
Source Layer: popp

◯Location
ID: loc
Type: Symbol
Source: v
Source Layer: loc

Icon layout propertiesのImage: public_building
Text layout propertiesのField: {name}　（{}の中に属性を記載するということだと思います）
Text layout propertiesのFont: sans　（今回のglyphsの設定だとsansとなります）

### Paint properties  (Type:Circle)

Populated PlacesのTypeはCircleであり、こちらのPaint propertiesについて詳しく見ていきます。
ポイントを円で指定します。ポイントは店舗の位置表示等に使用します。

https://maplibre.org/maplibre-style-spec/layers/#circle

Circleでは以下のPaint Properitesが利用可能です。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Color | circle-color | #000000（黒） |　塗りつぶす色 |
| Opacity | circle-opacity | 1（透過なし） |　塗りつぶしの透過度  |
| Stroke color | circle-stroke-color | #000000（黒） |　外周円の色  |
| Stroke opacity | circle-stroke-opacity | 1（透過なし） |　外周円の透過度 |
| Blur | circle-blur | 0(なし) |　ぼかしの指定  |
| Radius | circle-radius | 5 |　円の半径 |
| Stroke width | circle-stroke-width | 0 |　外周円の幅（円の外に広がるように大きくなる |
| Pitch scale | circle-pitch-scale | map |　傾きがついた時の円のスケーリング、遠近法のように表示されるかどうか(mapの場合は変動、viewportの場合は変動なし） |
| Translate | circle-translate | [0, 0] |　オフセット（位置を基準点からの距離で表した値）の指定。例えば、[3, 5]だと東に3ピクセル、南に5ピクセル移動  |
| Translate anchor | circle-translate-anchor | map |　map:地図と比較して移動　viewport:画面と比較して移動 ※circle-translateの設定が必要  |
| Pitch alignment | circle-pitch-alignment | viewport |　地図の傾きに合わせるかどうか（map:合わせる　viewport:合わせない） |



### ◯properties  (Type:Symbol)

次に、LocationのType(Symbol)のpropertiesについて詳しく見ていきます。
POIのアイコンやテキストラベルを表示する際に使用します。
テキストや画像を使うことができるため、MaputnikではLayout Properties及びPaint Propertiesが細かく分類されています。

:::note info
Symbol は、地図上に表示されるアイコン（icon）とテキスト（text）の両方を含む概念です。
Icon: 地図上のシンボルやマーカーとして表示される画像や図形
Text: ラベルや説明として表示されるテキスト
:::

◯Layout properties
- General layout properites
- Text layout properties
- Icon layout properties

◯Paint properties
- Text paint properties
- Icon paint properties

https://maplibre.org/maplibre-style-spec/layers/#symbol

ここからはMaputnikで使用されている上記のそれぞれのプロパティについて、細かく見ていきます。

### General layout properties(Symbol)
シンボルで利用可能なGeneral layout propertiesは以下の通りです。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Placement | symbol-placement | point |　ラベルがどのように配置されるか（point: POIの位置 line: 地物に沿って配置（LineStringとPolygonにのみ使用可能） line-center: 地物の中心（LineStringとPolygonにのみ使用可能）　） |
| Spacing | symbol-spacing | 250 |　２つの symbol が配置されている間の距離を指定（symbol-placementがlineである時のみ指定可能）  |
| Avoid edges | symbol-avoid-edges | false |　この設定はversion 0.42.0以降は自動で適用されるため、気にしなくてOK  |
| Z order | symbol-z-order | auto |　同一レイヤ内のシンボルが重なった場合の表示順 |

### Text layout properties(Symbol)
シンボルで利用可能なText layout propertiesは以下の通りです。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Field | text-field | なし |　テキストラベルに使う値を {field_name} のようなトークンで指定 |
| Font | text-font | Open Sans Regular,Arial Unicode MS Regular |　テキストの表示に使うFont stackを指定  |
| Size | text-size | 16 |　フォントサイズ  |
| Line height | text-line-height | 1.2 |　複数行における行間隔の広さを指定 |
| Padding | text-padding | 2 |　symbol間の衝突を防ぐためのテキストの境界の余白となるサイズを指定 |
| Allow overlap | text-allow-overlap | false |　trueなら他のシンボルと重ねってもテキストを表示 |
| Ignore placement | text-ignore-placement | false |　trueなら、テキストが衝突しても他の symbol は表示  |
| Pitch alignment | text-pitch-alignment | auto |　地図を傾けた時のテキストの方向 |
| Rotation alignment | text-rotation-alignment | auto |　アイコンの向きと連動したテキストの向きを指定 |
| Max width | text-max-width | 10 |　テキストを折り返す最大の横幅を指定 |
| Letter spacing | text-letter-spacing | 0 |　文字間隔  |
| Justify | text-justify | center |　テキストの整列方法(left: 左寄せ、 center: 中央寄せ、 right: 右寄せ)を指定 |
| Anchor | text-anchor | center |　テキストの配置位置を指定 |
| Max angle | text-max-angle | 45 |　symbol-placement = line or line-center の時のみ利用。テキストが曲線や斜めのラインに沿って配置される際に、隣り合う文字同士がどれくらい角度を変えるかを制限する設定 |
| Writing mode | text-writing-mode | なし |　symbol-placement = point の時のみ利用。文字の縦 or 横向きを指定 |
| Rotate | text-rotate | 0 |　テキストを時計回りに回転させる角度を指定  |
| Keep upright | text-keep-upright | true |　text-rotation-alignment = map でかつ symbol-placement = line or line-center の時のみ利用。 true ならテキストが上下反対にならないように垂直に反転することがある  |
| Transform | text-transform | none |　大文字・小文字の指定 |
| offset | text-offset | 0, 0 |　テキストを中心地点からずらすオフセットを指定 |
| Optional | text-optional | false |　icon-imageが必要。もしtrueならテキストが他のsymbolのテキストと衝突しているがアイコンが衝突していない場合、テキストなしでアイコンだけ表示 |
| Variable anchor | text-variable-anchor | なぃ |　テキストを表示する可能性を高めるために、テキスト配置位置の優先順位を設定 |
| Radial offset | text-radial-offset | 0 |　アンカーに沿ったオフセットを指定 |

### Icon layout properties(Symbol)
シンボルで利用可能なIcon layout propertiesは以下の通りです。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Image | icon-image | なし |　描画する Sprite の画像の名前を指定 |
| Allow overlap | icon-allow-overlap | false |　 trueなら、アイコンが他のシンボルと重なっても表示 |
| Ignore placement | icon-ignore-placement | false |　trueなら、アイコンが他のシンボルと重なったときにも他のシンボルを表示  |
| Optional | icon-optional | false |　もしtrueなら、アイコンが他のシンボルと衝突しているがテキストが衝突していない場合、アイコンなしでテキストだけ表示 |
| Rotation-alignment | icon-rotation-alignment | auto |　　アイコンの向きを指定 |
| Size | icon-size | 1 |　 アイコンの大きさを指定 |
| Text fit | icon-text-fit | none |　アイコンを関連付けられたテキストに合わせて拡大  |
| Text fit padding | icon-text-fit-padding | 0,0,0,0 |　icon-text-fit で決められたサイズの余白を時計回りに指定 |
| Rotate | icon-rotate | 0 |　画像を時計回りに回転させる角度を指定 |
| Padding | icon-padding | 2 |　アイコンの重なりを判断する余白の大きさを指定  |
| Keep upright | icon-keep-upright | false |　 trueなら、アイコンが上下反対にならないようアイコンを反転させることがある |
| Offset | icon-offset | 0, 0 |　中心地点からアイコンのオフセットの距離を指定 |
| Anchor | icon-anchor | center |　　アイコンの配置位置を指定 |
| Pitch alignment | icon-pitch-alignment | auto |　 地図を傾けた時のアイコンの方向 |


### Text paint properties(Symbol)
シンボルで利用可能なText paint propertiesは以下の通りです。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Color | text-color | #000000（黒）	 |　テキストの色 |
| Opacity | text-opacity | 1（透過なし） |　テキストの透過度 |
| Halo color | text-halo-color | rgba(0, 0, 0, 0)黒の透明 |　テキストを囲う色  |
| Halo width | text-halo-width | 0 |　フォントのアウトラインを囲う長さ。最大幅はフォントサイズの四分の一 |
| Halo blur | text-halo-blur | 0 |　テキストの囲いが外側にフェードアウトしていくときの長さを指定 |
| Translate | text-translate | 0, 0 |　テキストの中心位置が初期値からどれだけ移動させるかを指定 |
| Translate anchor | text-translate-anchor | map |　上記のTranslateをmapに関連付けるか、viewportに関連付けるかを指定 |


### Icon paint properties(Symbol)
シンボルで利用可能なIcon paint propertiesは以下の通りです。

| Maputnikのプロパティ名  | MapLibre GL JS | デフォルト値 | 説明 |
|:-:|:-:|:-:|:-:|
| Color | icon-color | #000000（黒）	 |　アイコンの色、SDFアイコンのみ利用可能 |
| Opacity | icon-opacity | 1（透過なし） |　アイコンの透過度 |
| Halo color | icon-halo-color | rgba(0, 0, 0, 0)黒の透明 |　アイコンの囲いの色を指定。SDFアイコンのみ利用可能  |
| Halo width | icon-halo-width | 0 |　アイコンのアウトラインを囲う長さ |
| Halo blur | icon-halo-blur | 0 |　アイコンの囲いが外側にフェードアウトしていくときの長さを指定 |
| Translate | icon-translate | 0, 0 |　アイコンの中心位置が初期値からどれだけ移動させるかを指定 |
| Translate anchor | icon-translate-anchor | map |　上記のTranslateをmapに関連付けるか、viewportに関連付けるかを指定 |

以下のサイトから上記で設定したスタイルの地図がMaputnikで見られます。
https://maputnik.github.io/editor/?style=https://k96mz.github.io/20240830symbol/style.json

このように、MaputnikのURLの後ろにスタイルのURLをつけることで、該当スタイルを読み込んだ状態のMaputnikを開く事ができます。

### 作成したスタイルファイル

```json: style.json
{
  "version": 8,
  "name": "Empty Style",
  "metadata": { "maputnik:renderer": "mlgljs" },
  "sources": {
    "v": {
      "type": "vector",
      "tiles": [
        "https://k96mz.github.io/20240830symbol/Data/VTpractice/{z}/{x}/{y}.pbf"
      ],
      "minzoom": 0,
      "maxzoom": 3
    }
  },
  "sprite": "https://k96mz.github.io/20240830symbol/sprite/sprite",
  "glyphs": "https://k96mz.github.io/20240830symbol/font/{fontstack}/{range}.pbf",
  "layers": [
    {
      "id": "bg",
      "type": "background",
      "maxzoom": 3,
      "paint": { "background-color": "rgba(147, 154, 255, 1)" }
    },
    {
      "id": "landmass",
      "type": "fill",
      "source": "v",
      "source-layer": "landmass",
      "maxzoom": 3,
      "paint": { "fill-color": "rgba(137, 243, 195, 1)" }
    },
    {
      "id": "coastl",
      "type": "line",
      "source": "v",
      "source-layer": "coastl",
      "maxzoom": 3
    },
    {
      "id": "bndl",
      "type": "line",
      "source": "v",
      "source-layer": "bndl",
      "maxzoom": 3
    },
    {
      "id": "popp",
      "type": "circle",
      "source": "v",
      "source-layer": "popp",
      "maxzoom": 3,
      "paint": { "circle-color": "rgba(203, 140, 140, 1)" }
    },
    {
      "id": "loc",
      "type": "symbol",
      "source": "v",
      "source-layer": "loc",
      "maxzoom": 3,
      "layout": {
        "icon-image": "public_building",
        "text-field": "{name}",
        "text-font": ["sans"]
      }
    }
  ],
  "id": "zc423ap"
}
```

### 作成した地図

![スクリーンショット 2025-01-14 14.48.19.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3684675/944240a6-47a4-3821-45b6-a18e9846e445.jpeg)

作成した地図は[こちら](https://k96mz.github.io/20240830symbol/index.html)から見られます。

# まとめ
MaputnikでのSymbolのスタイル編集について詳しく記載しました。Symbolについては、多くの設定がありややこしいですが、それだけ大事なものという気がします。本格的にweb地図を作成する際には、シンボルの重なりなど考慮することが多そうです。

# Reference

https://smellman.github.io/vector_tile_book/docs/styling/maputnik.html

https://qiita.com/k96mz/items/1ac756935b685d20dd7c

https://qiita.com/k96mz/items/1f7e0f8e6e41e6a78cc6

https://qiita.com/k96mz/items/b2ddc3df7d4000f52c39