IIIF (International Image Interoperability Framework) Presentation API v3のマニフェストに含まれるアノテーション座標(xywh形式)を、Leaflet-IIIFを使用したマップビューアー上で正確に表示する方法について解説します。
この問題は一見シンプルに見えますが、Leaflet-IIIFの内部動作を理解しないと正確な座標変換ができません。
問題の背景#
IIIFマニフェストのアノテーション形式#
IIIF Presentation API v3では、アノテーションの対象領域は以下のようなxywh形式で指定されます:
このxywh=41012,81,115,49は:
- x: 41012(左端のピクセル位置)
- y: 81(上端のピクセル位置)
- w: 115(幅)
- h: 49(高さ)
を意味します。これらは元画像のピクセル座標 です。
Leaflet-IIIFの座標系#
Leaflet-IIIFは、IIIF Image APIで提供される高解像度画像をタイル形式で表示するLeafletプラグインです。内部的には:
CRS.Simple座標参照系を使用- 画像を複数のズームレベルで縮小して管理
- ズームレベルごとに異なる座標スケールを使用
この複雑な座標系のため、単純にmap.unproject()やpointToLatLng()を使っても正しい位置に配置できません。
試行錯誤の過程#
失敗した試み1: map.unproject()の直接使用#
問題点 : unproject()は現在のマップの座標系を前提としており、Leaflet-IIIFが内部で使用している座標系とは異なります。
失敗した試み2: マップ境界からの比例計算#
問題点 : Leaflet-IIIFは画像のアスペクト比を保持するため、マップ境界と実際の画像境界は異なります。
失敗した試み3: アスペクト比を考慮した境界計算#
問題点 : この方法でも近い位置には配置できますが、Leaflet-IIIFが内部で使用している縮小された画像サイズを考慮していないため、わずかにずれが生じます。
正解:Leaflet-IIIFの_fitBoundsメソッドを再現#
Leaflet-IIIFのソースコードを分析#
Leaflet-IIIFの_fitBoundsメソッドを見ると、以下のロジックで画像を配置していることがわかります:
重要なポイント :
_getInitialZoom()で最適なズームレベルを計算_imageSizes[initialZoom + offset]で、そのズームレベルでの縮小された画像サイズ を取得- その縮小されたサイズを使って
pointToLatLng()で座標変換
正しい実装#
完全な実装例#
なぜこの方法が正しいのか#
ズームレベルごとの画像サイズ#
Leaflet-IIIFは、パフォーマンスのために画像を複数のズームレベルで管理します:
initialZoomの役割#
_getInitialZoom()は、マップのサイズに対して最適なズームレベルを計算します:
このズームレベルで、画像がマップにぴったり収まるように配置されます。
offsetの意味#
offsetは、_imageSizes配列のインデックスとズームレベルの対応を調整するためのものです:
例えば、maxNativeZoom = 8で_imageSizes.length = 9の場合、offset = 0となります。
デバッグのヒント#
座標変換がうまくいかない場合、以下の情報をコンソールに出力して確認してください:
0
まとめ#
Leaflet-IIIFでIIIFアノテーションを正確に表示するには:
- Leaflet-IIIFの内部ロジックを理解する :
_fitBoundsメソッドがどのように画像を配置しているか - 縮小された画像サイズを使用する : 元画像サイズではなく、
_imageSizes[initialZoom + offset]を使用 - 同じズームレベルで変換する :
pointToLatLng(point, initialZoom)で変換
この方法により、ピクセル単位で正確にアノテーションを配置できます。
参考資料#
プロジェクト情報#
- 使用ライブラリ:
- Leaflet 1.9.4
- Leaflet-IIIF 3.0.0
- 作成日: 2025年10月19日