この記事は、YAMAPエンジニアAdvent Calendar 2022 の12月25日の記事です。
はじめに
YAMAPにはMapboxを使って地図を表示する機能があり、地図上にサービス独自のアイコンを表示するためにはアイコン画像をMapbox Styleに登録する必要があります。
アイコン画像はMapboxのダッシュボードを使うと登録できますが、アイコン画像のオリジンは我々のサービス内で管理されているので二重管理はしたくありません。
そこでサービス内で管理されているアイコン画像を Mapbox Style API を使用してMapboxと同期することを考えます。
YAMAPのバックエンドシステムはRubyで実装されているため mapbox-sdk-rb を使うことを第一に検討しましたが現時点で Style API に対応しておらず、プルリクエストを作ることを検討しましたがこれもコードベースを確認したところ簡単ではなさそうであるとわかったので、Mapboxライブラリを自ら作ることとしました。
Mapboxkit
作成した gem は Mapboxkit です。
現時点では主に Mapbox Style API のすべてをサポートしています。
使い方
MAPBOX_ACCESS_TOKEN 環境変数にアクセストークンが設定されていれば、とくに gem の設定など必要ありません。
# スタイル一覧を取得する
Mapboxkit.styles('__your_username')
明示的にアクセストークンを設定したい場合は configure メソッドを使用できます。
Mapboxkit.configure do |config|
config.access_token = '__YOUR_ACCESS_TOKEN__'
end
もし一つのアプリケーションで複数のアクセストークンを扱いたい場合はクライアントインスタンスを直接作ることで解決できます。
client1 = Mapboxkit::Client.new(access_token: '__READONLY_ACCESS_TOKEN__')
client2 = Mapboxkit::Client.new(access_token: '__READWRITE_ACCESS_TOKEN__')
client1.styles('__your_username')
client2.create_style('__your_username', payload)
Mapbox Style に画像を登録する
YAMAPでは管理画面から100種類程度のアイコン画像を一つのzipファイルに圧縮し一度にアップロードすることができます。
そこでこのアップロードをトリガーにしてMapboxのスタイルにも同期します。
以下は実装は架空のものでYAMAPのそれとは大きく違いますが、おおむねこのような感じで実装できるはずです。
# Table name: `map_icons`
#
# ### Columns
#
# Name | Type | Attributes
# ----------------- | ------------------ | ---------------------------
# **`id`** | `bigint` | `not null, primary key`
# **`name`** | `string` | `not null`
# **`created_at`** | `datetime` | `not null`
# **`updated_at`** | `datetime` | `not null`
class MapIcon < ApplicationRecord
has_one_attached :svg # Mapbox Style のアイコン画像は SVG形式です
end
class MapIconsForm
def initialize(zip_file:)
@zip_file = zip_file
end
def save
map_icons = create_or_update_from(@zip_file)
SyncAllMapIconsJob.perform_later
map_icons
end
def create_or_update_from(zip_file)
# zip_file を展開して MapIcon を作成・更新する
# (省略)
end
end
class SyncAllMapIconsJob < ApplicationJob
USERNAME = '__USERNAME__'
STYLE_ID = '__STYLE_ID__'
def self.call = new.call
def call
# Mapbox Style の画像登録APIでは一度に登録できる画像は25件に制限されている
MapIcon.in_batches(of: 25) do |map_icons|
upload_map_icons(map_icons.to_a)
end
# 画像を反映するために Mapbox Style の Sprite ID を更新する
refresh_sprite
end
private
# アイコン画像をアップロードする
#
# @param map_icons Array<MapIcon>
# @param payloads Array<Hash<io: IO, filename: String>>
def upload_map_icons(map_icons, payload = [])
map_icon = map_icons.shift
unless map_icon
return Mapboxkit.add_images_to_sprite(USERNAME, STYLE_ID, payload)
end
map_icon.svg.open do |file|
payload << { io: file, filename: map_icon.name }
upload_map_icons(map_icons, payload)
end
end
def refresh_sprite
# 以下のドキュメントの指示に従い、最新のstyleデータを取得し modified created フィールドを削除する
# see: https://docs.mapbox.com/api/maps/styles/#update-a-style
base_params = Mapboxkit.style(USERNAME, STYLE_ID)
.except('modified', 'created')
# sprite ID を更新する
style_params = .merge(sprite: "mapbox://styles/#{USERNAME}/#{STYLE_ID}/{sprite_id}")
Mapboxkit.update_style(USERNAME, STYLE_ID, style_params)
end
end
map_icons_form = MapIconsForm.new(zip_file:)
map_icons_form.save
はまったこと
新しい画像がスプライト画像に反映されない
画像をMapbox Styleに登録に成功するが、本番環境に全く反映されないということがありました。
調査したところどうやら Mapbox Style の Sprite ID が画像を登録しても一切更新されておらず、これでは古い画像を参照し続けるようでした。
そこで上の疑似コードの以下の部分をあとから追加し呼び出すようにしたところ、Sprite ID が更新され十数分ほど待つことでキャッシュが更新され本番環境に画像が反映されたことを確認できました。
def refresh_sprite
# 以下のドキュメントの指示に従い、最新のstyleデータを取得し modified created フィールドを削除する
# see: https://docs.mapbox.com/api/maps/styles/#update-a-style
base_params = Mapboxkit.style(USERNAME, STYLE_ID)
.except('modified', 'created')
# sprite ID を更新する
style_params = .merge(sprite: "mapbox://styles/#{USERNAME}/#{STYLE_ID}/{sprite_id}")
Mapboxkit.update_style(USERNAME, STYLE_ID, style_params)
end
まとめ
- Mapbox Style を API で操作するために gem を作成しました。
- 作成した gem を使い Mapbox Style への画像の同期処理を自動化しました。
さいごに
コードを正しく共通化や抽象化する能力はプログラマにとって必要なスキルでしょう。
メイン関数に書いていたコードの断片をループに置き換える、関数に切り出す、クラスやモジュールとして実装する。
日常的に行われるこれらの営みのその先にライブラリとしてプロジェクトの外に切り離していくというプロセスが続いているのだと思います。
より良いソフトウェアを作れるよう引き続き精進します。
おわり。