📌

IIIF Presentation API v2のIIIFコレクションで、ページネーションを使う

に公開

概要

IIIF Presentation API v2のIIIFコレクションで、ページネーションを使う機会がありましたので、備忘録です。

背景

IIIFコレクションでは、以下のように、複数のマニフェストファイル(およびコレクション)の一覧を提供することができます。

https://iiif.io/api/presentation/2.1/#collection

{
  "@context": "http://iiif.io/api/presentation/2/context.json",
  "@id": "http://example.org/iiif/collection/top",
  "@type": "sc:Collection",
  "label": "Top Level Collection for Example Organization",
  "viewingHint": "top",
  "description": "Description of Collection",
  "attribution": "Provided by Example Organization",

  "manifests": [
    {
      "@id": "http://example.org/iiif/book1/manifest",
      "@type": "sc:Manifest",
      "label": "Book 1"
    }
  ]
}

この時、対象とするマニフェストファイルが多数になった場合、一つのIIIFコレクションでは配信が難しくなりました。

これに対して、以下でページネーションに関する仕様がありましたので、こちらを使ってみます。

https://iiif.io/api/presentation/2.1/#paging

ページネーション

上記のページでは、以下のような例が紹介されていました。

{
  "@context": "http://iiif.io/api/presentation/2/context.json",
  "@id": "http://example.org/iiif/collection/top",
  "@type": "sc:Collection",
  "label": "Example Big Collection",

  "total": 9316290,
  "first": "http://example.org/iiif/collection/c1"
}
{
  "@context": "http://iiif.io/api/presentation/2/context.json",
  "@id": "http://example.org/iiif/collection/c1",
  "@type": "sc:Collection",

  "within": "http://example.org/iiif/collection/top",
  "startIndex": 0,
  "next": "http://example.org/iiif/collection/c2",

  "manifests": [
    // Manifests live here ...
  ]
}

まず、一つの目のJSONでコレクション全体のマニフェストファイル数totalを示し、さらにはじめの部分マニフェストファイル群へのリンクをfirstで提示します。

firstで指定されたコレクションが2つ目のJSONであり、startIndexnextを使って、現在の位置と次の部分マニフェストファイル群へのリンクを示すようでした。

DrupalとElasticsearchの組み合わせによる実装例

上記のようなページネーションについて、DrupalとElasticsearchの組み合わせによる実装例を紹介します。以下の記事で紹介したように、drupal/elasticsearch_connectorを使って、DrupalとElasticsearchを接続していることを前提とします。

https://zenn.dev/nakamura196/articles/971f4805929a5d

Elasticsearchでは、fromを使ってオフセットを指定することができますが、max_result_window(デフォルトでは、10,000件)を超える場合、正しく機能しません。

そこでsearch_afterを使って、特定のコンテンツに続くコンテンツを取得することができます。

例えば、特定のソート条件下で、20,000件目のコンテンツのID(field_id)を取得し、以下のようなクエリにより、20,001件目以降のコンテンツを取得できます。searchAfterに20,000件目のコンテンツのIDが入っています。

  const query: SearchQuery = {
    _source: ['title', 'field_manifest', 'field_id'],
    size,
    sort: [{ field_id: 'asc' }],
  };

  if (searchAfter) {
    query.search_after = [searchAfter];
  }

ただし、Elasticsearch単独で特定のソート条件下において、デフォルトのmax_result_windowを使う場合、例えば20,000件目のコンテンツのID(field_id)を取得するには工夫が必要になります。

そこで、特定のソート条件下におけるxxx件目のコンテンツの取得、を行う部分については、DrupalのREST APIを使用しました。DrupalのViewsを使い、オフセットでユーザが指定指定できるようにした上で、1件のコンテンツを返却できるようにしました。

例:/api/xxx?offset=20000

これにより、search_afterに必要なコンテンツのIDを取得することができ、上述したElasticsearchへのクエリと組み合わせることで、ページネーションを含むIIIFコレクションを構築することができました。

参考

上記で実装したIIIFコレクションをPresentation API Validatorにかけてみました。

https://presentation-validator.iiif.io/

結果、上述したような、1つ目のコレクション全体を示すJSONでは、以下の警告が表示されました。

WARNING: Setting non-standard field 'first' on resource of type 'sc:Collection'
WARNING: Setting non-standard field 'total' on resource of type 'sc:Collection'

また、2つ目のコレクションの一部の示すJSONでは、以下の警告が表示されました。

WARNING: Setting non-standard field 'next' on resource of type 'sc:Collection'
WARNING: Setting non-standard field 'startIndex' on resource of type 'sc:Collection'

さらに、1つ目のコレクション全体を示すJSONについては、Universal ViewerやMiradorでもうまく表示させることができませんでした。

今回紹介した実装に問題があるのか等、引き続き調査してみたいと思います。

まとめ

IIIFコレクションを使って、多数のマニフェストファイル(またはサブコレクション)を提示する際に、本記事が参考になりましたら幸いです。

Discussion