ホーム 記事一覧 ブック DH週間トピックス 検索 このサイトについて
English

最新の記事

Annotoriousの描画モードがproduction buildでだけ壊れる

Annotoriousの描画モードがproduction buildでだけ壊れる

はじめに ある日、Vercelにデプロイした IIIF アノテーションエディタで、アノテーションが一切付与できなくなっていることに気づきました。ローカルの開発サーバーでは正常に動作するのに、本番環境でだけ描画モードに入れない。コンソールエラーも出ない。UIのボタンは正しく切り替わるのに、画像上でドラッグしても何も起きない——。 原因は、package.json のキャレット指定(^)による Annotorious の自動アップグレードと、v3.7.13 での状態管理ライブラリ移行が webpack の production build で引き起こす不具合でした。 この記事では、調査過程から根本原因の特定、そして得られた教訓までをまとめます。 環境 フレームワーク : Next.js 15 (App Router) 画像ビューア : OpenSeadragon 5 アノテーション : Annotorious v3 (@annotorious/react + @annotorious/openseadragon) バンドラ : webpack(Next.js 内蔵) デプロイ先 : Vercel 症状 本番環境(Vercel)で以下の症状が発生しました。 矩形・ポリゴンの描画ツールボタンをクリックすると、UIの状態は正しく切り替わる (React の state 更新は正常) しかし Annotorious が描画モードに入らない ——カーソルが crosshair に変わらず auto のまま 画像上でクリック&ドラッグしてもアノテーションが作成されない コンソールにエラーは一切表示されない Annotorious のアノテーションレイヤー要素(a9s-gl-canvas)自体は DOM 上に正しく描画されている ローカルの next dev では完全に正常動作するため、再現が困難な状況でした。 調査過程 1. Playwright による自動テスト まず Playwright を使って、デプロイ済みサイトに対する自動テストを実施しました。 ...

GakuNin RDM Search API (`/api/v1/search/`) 調査メモ

GakuNin RDM Search API (`/api/v1/search/`) 調査メモ

調査日 : 2026-02-24 対象 : GakuNin RDM (GRDM) の Search API ソースコード : RCOSDP/RDM-osf.io(website/search/ ディレクトリ) 開発者ガイド : RCOSDP/RDM-developer-guide 注意 : Search API の公式ドキュメントは確認できませんでした。本稿は API の実際の挙動とソースコードの両方に基づく調査記録です。 概要 GakuNin RDM は OSF (Open Science Framework) のフォークであり、ソースコードは GitHub (RCOSDP/RDM-osf.io) で公開されています。検索機能の実装は website/search/ ディレクトリにあり、主に以下のファイルで構成されています。 ファイル 役割 elastic_search.py インデックスのマッピング定義、ドキュメントの登録・更新 views.py API エンドポイントのハンドラ util.py build_private_search_query() 等のクエリ構築 search.py 上位インターフェース POST https://rdm.nii.ac.jp/api/v1/search/ Authorization: Bearer <パーソナルアクセストークン> 日本語環境では Elasticsearch の kuromoji_analyzer が使用されています(ソースコードで確認)。 リクエスト形式 { "api_version": {"vendor": "grdm", "version": 2}, "elasticsearch_dsl": { "query": { "filtered": { "query": { "query_string": { "default_field": "_all", "query": "検索キーワード" } } } }, "from": 0, "size": 10 }, "highlight": "title:30,name:30,user:30,text:124,comments.*:124", "sort": "modified_desc" } パラメータ 説明 備考 api_version vendor: "grdm", version: 2 version は 1 と 2 をサポートしています(ソースコードで確認) elasticsearch_dsl.query Elasticsearch Query DSL filtered 形式(ES 2.x 系構文)です from / size ページネーション size=100 まで動作を確認しています。match_all + size>50 では 500 エラーになります highlight フィールド名:文字数 形式 GRDM 独自フォーマットです。ワイルドカード(comments.*)も使えます sort ソート順 後述します sort の選択肢 ソースコード(util.py の build_private_search_query)によると、以下のソート対象が定義されています。 ...

DH週間トピックス — 2026年2月第4週

DH週間トピックス — 2026年2月第4週

デジタル人文学(DH)関連の新規ツール開発・公開情報を週次でまとめています。 NDLOCR-Lite Web版の公開 国立国会図書館のAI-OCRツール「NDLOCR-Lite」のWebブラウザ版「NDLOCR-Lite Web」が公開されました。この新版では、ブラウザ上で手軽に画像やPDFのOCR処理を試すことができ、画像や認識テキストが外部に送信されることなく、ローカルで処理が完結するとのことです。 WebWorkerを使った並列処理化(最大8スレッド)により、1枚あたり数秒での認識が可能で、100ページ程度の文庫本であれば数分で処理が完了すると説明されています。また、AndroidのChromeでの動作確認がされており、モバイル環境での利用も可能なようです。 NDLOCR-Lite Web 開発者によると、読み順推定アルゴリズムに横書きテキストでの不具合が確認されており、修正作業が進められているとのことです。 @yuta1984の投稿およびGitHubリポジトリへの頻繁なコミットから確認されました。 本記事は X投稿・GitHub更新・カレントアウェアネス・ポータルから自動収集した情報を基に生成しています。

Google Workspace 管理者権限なしで Google Groups のメンバーを API で一括管理する

Google Workspace 管理者権限なしで Google Groups のメンバーを API で一括管理する

Google Groups のメンバーを定期的に入れ替える運用をしている組織は多いと思います。手作業での登録・削除は手間がかかるため、API で自動化したいところです。 Google Groups のメンバーを API で管理する方法としては、Admin SDK (Directory API) がよく紹介されています。一方で、Admin SDK は Google Workspace の管理者権限が必要であり、利用できる場面が限られます。 本記事では、管理者権限がない環境でも利用できる Cloud Identity Groups API を使って、Google Groups のメンバーを一括管理する方法を紹介します。 API の選択肢 Google Groups のメンバーを API で管理するには、主に以下の 2 つの方法があります。 Admin SDK (Directory API) もっとも広く紹介されている方法です。 service = build('admin', 'directory_v1', credentials=creds) service.members().list(groupKey='group@example.com').execute() 利用するには、以下のいずれかが必要です: Google Workspace の管理者アカウント サービスアカウント + ドメイン全体の委任(Domain-Wide Delegation) (管理コンソールでの設定が必要) 管理者権限がない場合は 403 Forbidden が返されます。 Cloud Identity Groups API Cloud Identity Groups API は、グループのオーナーやマネージャーであれば、管理者権限なしでメンバーの操作が可能です。本記事ではこちらの方法を使います。 両者の違いを以下にまとめます。 ...

Archivematica における非DCメタデータの登録検証 ── source-metadata.csv を使ったEADの組み込み

Archivematica における非DCメタデータの登録検証 ── source-metadata.csv を使ったEADの組み込み

Archivematica では、Dublin Core(DC)以外のメタデータスキーマもAIPのMETS.xmlに組み込むことができます。本ガイドでは、source-metadata.csv を使って EAD や MODS などの非DCメタデータをTransferに含め、AIPに正しく格納されるかをAPI経由で検証します。 目次 背景と目的 source-metadata.csv の仕組み XML Validation 機能 検証1: MODS単独でのメタデータ登録 検証2: EAD + MODS の同時登録 METS.xml における非DCメタデータの格納形式 検証3: Reingest によるメタデータ追加 まとめ 背景と目的 Archivematica の標準的な Transfer では、metadata/metadata.csv に記述した Dublin Core メタデータが METS.xml に <dmdSec> として格納されます。しかし、実際のデジタルアーカイブ運用では、以下のようなユースケースで DC 以外のメタデータスキーマを扱う必要があります。 EAD(Encoded Archival Description) : アーカイブズの階層記述で広く使われる標準 MODS(Metadata Object Description Schema) : 図書館資料の詳細記述に使われるスキーマ LIDO : 博物館・美術館資料の記述標準 MARC21 : 図書館の目録データフォーマット Archivematica は source-metadata.csv というCSVファイルを通じて、任意の XML メタデータを Transfer に紐付け、AIP の METS.xml に <dmdSec> として格納する機能を提供しています。本ガイドでは、この機能を API 経由で実際に検証します。 ...

AtoM REST APIによるデジタルアーカイブ構築の検証

AtoM REST APIによるデジタルアーカイブ構築の検証

はじめに AtoM (Access to Memory) は、アーカイブ機関向けのオープンソースWebアプリケーションです。世界中の図書館・文書館・博物館で、資料記述の管理に利用されています。 AtoMの操作は通常Web UIから行いますが、REST APIを使えば外部システムとの連携やバッチ処理が可能になります。本記事では、現実的な業務シナリオ に沿ってAPIを一通り試し、Web UIでの反映も確認していきます。 APIプラグインの開発経緯や実装の詳細は、別記事 AtoMのREST APIを拡張するプラグインを開発した話 をご覧ください。 利用するAPI arRestApiPlugin (AtoM標準): 資料記述(Information Object)のCRUD arExtendedApiPlugin (独自開発): 所蔵機関・典拠レコード・受入記録・タクソノミー・機能記述・デジタルオブジェクトの操作 全28エンドポイントの一覧は開発記事を参照してください。 事前準備 # AtoMのURL(環境に合わせて変更) export ATOM_URL="http://localhost:63001" # APIキー(Admin > Settings > Global > API key で設定) export API_KEY="your-api-key-here" APIキーはAtoMの管理画面(Settings > Global)で設定・確認できます。 業務シナリオ:「橋本市立図書館デジタルアーカイブの構築」 ストーリー 橋本市立図書館が、郷土史研究家の山田花子氏から寄贈された橋本市の古写真コレクション(明治〜昭和期)のデジタルアーカイブを構築する。 以下の手順で、アーカイブの構築に必要な一連の作業をAPIで実行していきます。 Step 業務内容 API 0 初期状態を確認する GET /api/summary 1 所蔵機関を登録する POST /api/repositories 2 所蔵機関の情報を確認する GET /api/repositories/:slug 3 連絡先情報を追加する PUT /api/repositories/:slug 4 寄贈者を登録する POST /api/actors 5 寄贈者の情報を確認する GET /api/actors/:slug 6 受入記録を作成する POST /api/accessions 7 処理ステータスを更新する PUT /api/accessions/:slug 8 タクソノミー(分類語彙)を確認する GET /api/taxonomies 9 主題分類を追加する POST /api/taxonomies/:id/terms 10 分類名を修正する PUT /api/taxonomies/terms/:id 11 資料記述を作成する POST /api/informationobjects 12 デジタル画像を添付する POST /api/informationobjects/:slug/digitalobject 13 最終状態を確認する GET /api/summary シナリオ完了後、「追加機能の紹介」セクションで以下も解説します: ...

AtoMのREST APIを拡張するプラグインを開発した話

AtoMのREST APIを拡張するプラグインを開発した話

はじめに AtoM (Access to Memory) は、アーカイブ機関向けのオープンソースWebアプリケーションです。ISAD(G)、ISAAR(CPF)、ISDFなどの国際標準に準拠した記述管理機能を提供しており、世界中の図書館・文書館・博物館で利用されています。 AtoMには arRestApiPlugin という標準のREST APIプラグインが同梱されていますが、以下の制約があります: 情報オブジェクト(資料記述)のCRUD が中心で、カバー範囲が限定的 所蔵機関(Repository) 、典拠レコード(Actor) 、受入記録(Accession) のAPIがない タクソノミー (分類語彙)の操作APIがない デジタルオブジェクト のアップロードAPIが実用的でない 機能記述(Function) のAPIがない これでは、外部システムとの連携やバッチ処理による大量登録といった業務ニーズに応えられません。 本記事では、これらの課題を解決するために開発した arExtendedApiPlugin の実装について解説します。 このプラグインを使って実際に業務シナリオを実行した記事もあります:APIで構築する図書館デジタルアーカイブ — AtoM業務シナリオ実践ガイド arExtendedApiPlugin の概要 エンドポイント一覧(全28エンドポイント) リソース メソッド エンドポイント 説明 サマリー GET /api/summary 各エンティティの件数 所蔵機関 GET /api/repositories 一覧(検索・ページネーション) GET /api/repositories/:slug 詳細取得 POST /api/repositories 新規作成 PUT /api/repositories/:slug 更新 DELETE /api/repositories/:slug 削除 POST /api/repositories/:slug/logo ロゴアップロード DELETE /api/repositories/:slug/logo ロゴ削除 典拠レコード GET /api/actors 一覧 GET /api/actors/:slug 詳細取得 POST /api/actors 新規作成 PUT /api/actors/:slug 更新 DELETE /api/actors/:slug 削除 受入記録 GET /api/accessions 一覧 GET /api/accessions/:slug 詳細取得 POST /api/accessions 新規作成 PUT /api/accessions/:slug 更新 DELETE /api/accessions/:slug 削除 タクソノミー GET /api/taxonomies 全タクソノミー一覧 POST /api/taxonomies/:id/terms 用語追加 PUT /api/taxonomies/terms/:id 用語更新 DELETE /api/taxonomies/terms/:id 用語削除 機能記述 GET /api/functions 一覧 GET /api/functions/:slug 詳細取得 POST /api/functions 新規作成 PUT /api/functions/:slug 更新 DELETE /api/functions/:slug 削除 デジタルオブジェクト POST /api/informationobjects/:slug/digitalobject アップロード Note : 資料記述(Information Object)のCRUDは、既存の arRestApiPlugin が提供する POST/GET /api/informationobjects を使用します。 ...

AlfrescoをDockerで起動し、REST APIでレコード管理のライフサイクルを体験する

AlfrescoをDockerで起動し、REST APIでレコード管理のライフサイクルを体験する

概要 本記事では、Alfresco Governance Services Community Edition(以下AGS)の最新版(25.3.0)をDockerで起動し、REST APIを使ってレコード管理の一連のライフサイクルを体験します。 具体的には、以下の業務シナリオを想定します。 シナリオ: 契約書管理 業務部門が契約書を作成・登録する レコード管理者がレコードとして宣言し、ファイルプランに分類する 保持スケジュール(Retention Schedule)を設定する 契約終了後、カットオフ(現用→非現用)を実行する 保持期間(3年)の経過後、廃棄する 訴訟対応が発生した場合、ホールド(凍結)により廃棄を停止する 以下の前回の記事をベースに、最新版での構築手順とAPIの使い方を紹介します。 https://zenn.dev/nakamura196/articles/8da7161ff3df30 環境 acs-deployment: v10.2.0(2026年2月リリース) Alfresco Governance Repository Community: 25.3.0 Alfresco Governance Share Community: 25.3.0 Alfresco Search Services: 2.0.17 Traefik: 3.6 PostgreSQL: 16.5 セットアップ リポジトリのクローン git clone https://github.com/Alfresco/acs-deployment cd acs-deployment git checkout v10.2.0 cd docker-compose compose fileの作成 community-compose.yamlをベースに、Governance Services用のcompose fileを作成します。変更点は以下の3つです。 1. イメージの差し替え サービス 変更前 変更後 alfresco alfresco/alfresco-content-repository-community:25.3.0 alfresco/alfresco-governance-repository-community:25.3.0 share alfresco/alfresco-share:25.3.0 alfresco/alfresco-governance-share-community:25.3.0 2. 認証チケットのタイムアウト対策(後述) ...

DH週間トピックス — 2026年2月第3週

DH週間トピックス — 2026年2月第3週

デジタル人文学(DH)関連の新規ツール開発・公開情報を週次でまとめています。 koten-layout-detector v1.0.0およびv1.1.0がリリース 古典文献のレイアウト検出を行うツール「koten-layout-detector」のバージョン1.0.0が2026年2月20日にリリースされ、同日中にv1.1.0へのアップデートも行われました。このツールは古典文献の画像から文字領域やレイアウト要素を自動検出する機能を提供するものと推測されます。 koten-layout-detectorリポジトリ @yuta1984のGitHub更新情報より。 本記事は X投稿・GitHub更新・カレントアウェアネス・ポータルから自動収集した情報を基に生成しています。

IIIFマニフェストを用いたテキスト比較ツールの開発

IIIFマニフェストを用いたテキスト比較ツールの開発

はじめに 古典籍のデジタル化が進む中、異なる写本や校訂本のテキストを比較・分析するニーズが高まっています。本稿では、IIIF(International Image Interoperability Framework)マニフェストを活用し、2つの資料の画像とテキストを並べて比較できるWebアプリケーション「Text Comparison Tool」を紹介します。 デモサイト : https://iiif-text.vercel.app/ 背景と課題 デジタルアーカイブで公開されている古典籍には、IIIFマニフェストにテキストアノテーションが付与されているものがあります。しかし、2つの資料のテキストを並べて比較する手軽なツールは多くありません。 例えば、ある作品の校訂本と写本を比較する場合、以下のような作業が必要です: 画像を並べて目視で比較する テキストの差異を一文字ずつ確認する どの程度類似しているかを定量的に把握する これらを1つのツールで実現することを目指しました。 3つの比較モード 本ツールは、3つのモードで資料を比較できます。 1. 画像比較 OpenSeadragonを用いた高精細画像ビューアで、2つの資料の画像を左右に並べて表示します。ズーム・パン・回転に対応し、ページ送りも可能です。 2. テキスト差分(Diff) IIIFマニフェストに含まれるテキストアノテーションを抽出し、文字単位での差分をハイライト表示します。追加箇所は緑色、削除箇所は赤色の取り消し線で表示されます。 3. 編集距離(Levenshtein Distance) レーベンシュタイン距離に基づき、行単位でのテキスト類似度を算出します。結果はネットワークグラフとして可視化され、類似度の高い行同士がエッジで結ばれます。閾値スライダーにより、表示するエッジの最低類似度を調整できます。 技術スタック カテゴリ 技術 フレームワーク Next.js(App Router / Static Export) 言語 TypeScript スタイリング Tailwind CSS v4 UIコンポーネント Radix UI 画像ビューア OpenSeadragon ネットワーク可視化 vis-network 状態管理 Zustand 国際化 next-intl(日本語 / English) 差分検出 diff アーキテクチャ データフロー IIIFマニフェストURL ↓ fetchManifest() — マニフェスト取得・パース ↓ ComparisonValue — 画像URL群、テキスト配列、メタデータ ↓ Zustand Store — アプリケーション状態として保持 ↓ 各比較コンポーネントが消費・描画 fetchManifest()関数がIIIF Presentation API v3のマニフェストをパースし、各キャンバスの画像URLとテキストアノテーションを抽出します。抽出されたデータはZustandストアに格納され、各コンポーネントがリアクティブに参照します。 ...

Drupal の GitHub Webhook モジュールを改善しました。

Drupal の GitHub Webhook モジュールを改善しました。

Drupal の管理画面から GitHub Actions をトリガーするカスタムモジュール「GitHub Webhook」を改善しました。 https://github.com/nakamura196/Drupal-module-github_webhook 元は複数リポジトリ対応の基本的なモジュールでしたが、UI のタブ分離、権限の細分化、ワークフローステータス表示、自動トリガーなどの機能を追加しています。 改善前のモジュール 元のモジュールは、以下のような構成でした。 ファイル数 : 5ファイル(info.yml、routing.yml、links.menu.yml、permissions.yml、SettingsForm.php) 対応バージョン : Drupal 10 のみ リポジトリ : 複数対応済み(AJAX で動的追加・削除) 画面 : 設定とトリガーが同一画面(アコーディオン2つ) 権限 : access github webhook settings の1権限のみ(設定もトリガーも同じ権限) トークン管理 : パスワードフィールドに #default_value を設定(HTML ソースに平文で出力される) HTTP クライアント : new \GuzzleHttp\Client() を直接インスタンス化 例外クラス : use 文なしで catch ブロックに記述(名前空間の解決が不正) // 改善前: トークンが #default_value に設定されていた $form['settings']['github_token'] = [ '#type' => 'password', '#title' => $this->t('GitHub Token'), '#default_value' => $config->get('github_token'), // HTML に平文出力される ]; // 改善前: Guzzle クライアントを直接 new していた $client = new \GuzzleHttp\Client(); 変更の全体像 改善前後のファイル構成の比較です。* は変更、+ は新規追加を示します。 ...

Netlify CLIを使って不要なサイトを一括削除する

Netlify CLIを使って不要なサイトを一括削除する

大量のNetlifyサイトが溜まってきたので、CLIを使って一括削除した際の手順をまとめます。 背景 開発やテストで作成したNetlifyサイトが41個まで増えていました。現在使用しているサイトは数個だけだったため、古いサイトをまとめて削除することにしました。 環境 macOS Node.js netlify-cli v23.15.1 手順 1. Netlify CLIのインストール npm install -g netlify-cli 2. ログイン netlify login ブラウザが開き、Netlifyの認証画面が表示されます。認証を許可するとCLIにトークンが保存されます。 3. サイト一覧の取得 netlify sites:list JSON形式で取得する場合は --json オプションを付けます。 netlify sites:list --json Python等で整形して確認すると見やすくなります。 netlify sites:list --json | python3 -c " import json, sys sites = json.load(sys.stdin) print(f'合計: {len(sites)} サイト\n') for i, s in enumerate(sites): name = s.get('name', 'N/A') url = s.get('url', 'N/A') updated = s.get('updated_at', 'N/A')[:10] site_id = s.get('id', 'N/A') print(f'{i+1:3d}. {name}') print(f' URL: {url}') print(f' 更新日: {updated} ID: {site_id}') " 4. サイトの削除 個別に削除する場合: netlify sites:delete --force <site-id> --force を付けないと確認プロンプトが表示されます。 5. シェルスクリプトで一括削除 削除対象のサイトIDと名前を配列に入れて、ループで一括削除できます。 #!/bin/bash sites=( "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx:site-name-1" "yyyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy:site-name-2" "zzzzzzzz-zzzz-zzzz-zzzz-zzzzzzzzzzzz:site-name-3" ) deleted=0 failed=0 for entry in "${sites[@]}"; do id="${entry%%:*}" name="${entry##*:}" echo -n "削除中: $name ... " if netlify sites:delete --force "$id" 2>&1; then echo "OK" ((deleted++)) else echo "FAILED" ((failed++)) fi done echo "" echo "=== 完了 ===" echo "削除成功: $deleted" echo "失敗: $failed" APIを直接使う方法 CLIを使わずにREST APIを直接呼ぶことも可能です。 ...

Drupal 10 の管理画面からモジュール更新とコアアップデートを行う

Drupal 10 の管理画面からモジュール更新とコアアップデートを行う

共用サーバー上の Drupal 10.6.1 で、Backup and Migrate によるバックアップと Automatic Updates によるモジュール・コアの自動更新を設定した手順をまとめる。 現状確認 管理画面の「レポート > サイトの状態」を確認すると、3つの警告が出ていた。 Drupal コアの更新状況:期限切れ(バージョン 10.6.3 が入手可能) PHP APCu available caching:メモリ使用量が75%超え モジュールとテーマの更新状況:期限切れ 「レポート > 利用可能なアップデート」を見ると、以下のモジュールに更新があった。 Consumers 8.x-1.22 Geofield 8.x-1.66 Geofield Map 11.1.1 Leaflet 10.3.11 Backup and Migrate のインストール 更新作業の前に、まずバックアップ手段を用意する。 composer.phar require 'drupal/backup_migrate:^5.1' vendor/bin/drush en backup_migrate vendor/bin/drush cr インストール後、「管理 > 環境設定 > 開発 > バックアップと移動」からクイックバックアップが実行できるようになる。バックアップ元に「デフォルト Drupal データベース」、バックアップ保存先に「ダウンロード」を選択して「今すぐバックアップ」を押すと、データベースのバックアップファイルがダウンロードされる。 Automatic Updates のインストール Automatic Updates モジュールを使うと、管理画面からモジュールやコアのアップデートが行える。 composer.phar require 'drupal/automatic_updates:^3.1' vendor/bin/drush en automatic_updates vendor/bin/drush en automatic_updates_extensions Composer パスの設定 有効化直後、Automatic Updates の画面にエラーが表示された。 ...

Mirador 4 で外部マニフェストのウィンドウタイトルだけを差し替える

Mirador 4 で外部マニフェストのウィンドウタイトルだけを差し替える

背景 Mirador は IIIF 対応の画像ビューアで、複数の IIIF マニフェストを並べて比較閲覧できる。複数機関が公開するマニフェストを一画面に並べて表示する際、各ウィンドウのタイトルにはマニフェストの label がそのまま表示される。 しかし、自プロジェクト独自の名称をウィンドウタイトルとして表示したいケースがある。例えば、マニフェストの label が個別の冊次情報を含む長い文字列であるのに対し、資料群を示す短い名称で表示したい場合などである。 制約:マニフェストの中身は変えてはいけない 他機関が公開している IIIF マニフェストを読み込んで表示する以上、その中身を改変して表示することは避けたい。fetch のインターセプトや Mirador 内部状態の書き換えでマニフェスト JSON の label を差し替える方法もあるが、これは実質的にマニフェストの改変にあたる。 変更すべきは Mirador が画面上に描画したウィンドウのタイトル表示(DOM)だけ であり、マニフェストのデータ自体はオリジナルのまま保持したい。 試したアプローチと結果 1. Mirador.actions.receiveManifest による内部状態の書き換え // Mirador の store を監視し、マニフェスト読み込み後に label を書き換え store.subscribe(function () { var state = store.getState(); if (manifests[manifestId] && manifests[manifestId].json && !overridden[manifestId]) { overridden[manifestId] = true; var updatedJson = JSON.parse(JSON.stringify(manifests[manifestId].json)); updatedJson.label = customTitle; store.dispatch(Mirador.actions.receiveManifest(manifestId, updatedJson)); } }); 結果:動作しない。 unpkg から配信される Mirador 4 の UMD ビルドでは Mirador.actions が undefined であり、この API は利用できなかった。 ...

Next.js 15 で発生する `localStorage.getItem is not a function` エラーの原因と対処法

Next.js 15 で発生する `localStorage.getItem is not a function` エラーの原因と対処法

Node.js 25 + Next.js 15 で発生する localStorage.getItem is not a function エラーの原因と対処法 はじめに Next.js 15 のプロジェクトで npm run dev を実行したところ、以下のエラーが発生して開発サーバーが正常に動作しなくなりました。 ⨯ [TypeError: localStorage.getItem is not a function] { digest: '2892703879' } [TypeError: localStorage.getItem is not a function] ⨯ [TypeError: localStorage.getItem is not a function] { page: '/ja' } (node:2405) Warning: `--localstorage-file` was provided without a valid path コード上で localStorage を直接呼び出している箇所はなく、原因の特定に時間がかかりました。本記事では、このエラーの根本原因と対処法を解説します。 環境 Node.js : v25.2.1 Next.js : 15.3.8 next-intl : 4.3.5 OS : macOS (Darwin 25.2.0) エラーの根本原因 このエラーは Node.js 25 で導入された Web Storage API と Next.js 15 の組み合わせで発生します。 ...

SPARQL クライアントを Apache Jena Fuseki に対応させるときにハマった 3 つのこと

SPARQL クライアントを Apache Jena Fuseki に対応させるときにハマった 3 つのこと

Virtuoso / Dydra 向けに作られた SPARQL Explorer「Snorql」を Apache Jena Fuseki でも動くようにしました。SPARQL は W3C 標準ですが、エンドポイント実装ごとの挙動差は意外と大きいです。Fuseki 対応で直面した 3 つの問題と、その解決方法を記録します。 開発環境 Docker で Fuseki を起動し、ローカルで検証しました。 # docker-compose.yml services: fuseki: image: stain/jena-fuseki container_name: fuseki ports: - "3030:3030" environment: - ADMIN_PASSWORD=admin - FUSEKI_DATASET_1=test volumes: - fuseki-data:/fuseki volumes: fuseki-data: docker compose up -d # テストデータ投入 curl -X POST 'http://localhost:3030/test/data' \ -H 'Content-Type: text/turtle' \ --data-binary @testdata.ttl 1. DESCRIBE のレスポンス形式が違う 症状 Fuseki に DESCRIBE クエリを投げると、結果が画面に表示されません。コンソールには JSON パースエラーが出ていました。 ...

Snorql — 複数の SPARQL エンドポイントを手軽に探索できるブラウザ UI を公開しました

Snorql — 複数の SPARQL エンドポイントを手軽に探索できるブラウザ UI を公開しました

Snorql — A Browser-Based UI for Exploring Multiple SPARQL Endpoints https://nakamura196.github.io/snorql/ はじめに / Introduction SPARQL エンドポイントを手軽に試せるツールが欲しい ── そう思ったことはありませんか? Have you ever wanted a quick, easy way to try out SPARQL endpoints? SPARQL は Linked Open Data (LOD) を検索するための標準クエリ言語ですが、エンドポイントごとに UI が異なったり、そもそも UI が用意されていなかったりします。そこで、1 つの統一的な UI から複数のエンドポイントを切り替えて使える ツールとして Snorql を公開しました。 SPARQL is the standard query language for searching Linked Open Data (LOD), but each endpoint often has a different UI — or none at all. To solve this, I published Snorql, a tool that lets you switch between multiple endpoints from a single, unified UI. ...

Mirador ビューア埋め込み設定

Mirador ビューア埋め込み設定

IIIF画像の表示に Mirador ビューアを使用する方法について説明します。 参考実装 埋め込み方式は、Stanford University Libraries の Stanford Digital Repository を参考にしています。書誌情報の上部にビューアを埋め込み、メタデータと画像を同一ページで閲覧できるようにしています。 ファイル構成 apps/web/ ├── public/mirador/ │ └── index.html # Mirador ビューア本体 ├── src/components/item/ │ └── MiradorViewer.tsx # 埋め込みコンポーネント └── .env.local # 環境変数設定 URLパラメータ /mirador/index.html は以下のURLパラメータを受け付けます: パラメータ 説明 例 manifest IIIFマニフェストURL(必須)。セミコロン区切りで複数指定可能 https://example.com/iiif/manifest.json embed 埋め込みモード(trueで閉じるボタンと左側メニューを非表示) true theme テーマ(dark または light) dark lang 言語コード(デフォルト: ja) ja, en canvas 初期表示するキャンバスID - annotationState アノテーション表示モード(trueでサイドバー開く) true 使用例 /mirador/index.html?manifest=https://example.com/iiif/manifest.json&embed=true&theme=dark&lang=ja Mirador設定詳細 index.html の構成 DOCTYPE html> html lang="ja"> head> meta charset="utf-8" /> meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" /> title>Miradortitle> link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" /> head> body> div id="mirador" style="position: absolute; top: 0; bottom: 0; left: 0; right: 0">div> script src="https://unpkg.com/mirador@latest/dist/mirador.min.js">script> script> // 設定スクリプト script> body> html> URLパラメータの解析 var vars = {}; var param = location.search.substring(1).split("&"); for (var i = 0; i param.length; i++) { var keySearch = param[i].search(/=/); var key = ""; if (keySearch != -1) key = param[i].slice(0, keySearch); var val = param[i].slice(param[i].indexOf("=", 0) + 1); if (key != "") vars[key] = decodeURI(val); } ウィンドウ設定 var windows = []; if (vars["manifest"]) { var manifests = vars["manifest"]; var array = manifests.split(";"); // セミコロンで複数マニフェストを分割 for (var i = 0; i array.length; i++) { var manifest = decodeURIComponent(array[i]); var obj = { manifestId: manifest, thumbnailNavigationPosition: "far-right", // サムネイルを右端に表示 }; if (vars["canvas"]) { obj.canvasId = vars["canvas"]; // 初期表示キャンバス } windows.push(obj); } } ウィンドウ動作設定 var windowSettings = { allowClose: true, // 閉じるボタン表示 allowFullscreen: true, // 全画面ボタン表示 } // アノテーションモード if (vars["annotationState"]) { windowSettings.highlightAllAnnotations = true; windowSettings.sideBarOpen = true; windowSettings.defaultSideBarPanel = 'annotations'; } // 埋め込みモード: UIを簡素化 if (vars["embed"] === "true") { windowSettings.allowClose = false; windowSettings.allowMaximize = false; } ワークスペースコントロールパネル 左側のメニュー(マニフェスト追加、ワークスペース管理など)の表示制御: ...

Omeka SのIIIF Serverモジュールで、PLYファイルがIIIFマニフェストのitemsに出力されない問題の調査

Omeka SのIIIF Serverモジュールで、PLYファイルがIIIFマニフェストのitemsに出力されない問題の調査

概要 Omeka SのIIIF Serverモジュールで、PLYファイルがIIIFマニフェストのitemsとして出力されないが、GLBファイルは正常に出力される問題を調査しました。 前提条件:Omeka Sの設定 デフォルトでは、PLYファイルはOmeka Sにアップロードできません。以下の設定が必要です。 PLYファイルアップロード時のエラー デフォルト設定では、PLYファイルのメディアタイプ(application/octet-stream)と拡張子(.ply)が許可されていないため、アップロードエラーが発生します。 設定の追加 管理画面の「設定」→「セキュリティ」で以下を追加してください: 許可されるメディアタイプ : application/octet-stream を追加 許可されるファイル拡張子 : ply を追加 原因 PLYファイルの処理コードがモジュールに実装されていませんでした。 GLBファイルには明示的な拡張子チェックと型変換のコードが存在しますが、PLYファイルには同様のコードが存在しませんでした。 技術的詳細 GLBファイルの処理(修正前から存在) TraitMedia.php (format()メソッド) if ($mediaType === 'application/octet-stream') { $extension = strtolower(pathinfo((string) $this->resource->source(), PATHINFO_EXTENSION)); if ($extension === 'glb') { return 'model/gltf-binary'; } } IiifTypeOfMedia.php if ($mediaType === 'application/octet-stream') { $extension = strtolower(pathinfo((string) $media->source(), PATHINFO_EXTENSION)); if ($extension === 'glb') { return $mediaIiifTypes[$mediaId] = 'Model'; } } IIIFマニフェストのitems生成フロー メディアタイプの判定 (TraitMediaInfo.php) ...

3D Gaussian Splatting Viewer の開発 - Spark.jsを使ったブラウザ実装

3D Gaussian Splatting Viewer の開発 - Spark.jsを使ったブラウザ実装

3D Gaussian Splatting(3DGS)ファイルをブラウザで閲覧できるビューアを開発しました。本記事では、3DGSの概要と、通常のPLY Viewerでは表示できない理由、そして専用ビューアの実装について解説します。 デモ: https://3dtiles-viewer.vercel.app/3dgs-viewer.html?manifest=https://3dtiles-viewer.vercel.app/iiif/ply/manifest.json なぜ通常のPLY Viewerでは3DGSを表示できないのか 3DGSファイルは拡張子が.plyであるため、一見すると通常のPLYファイルと同じように扱えそうに見えます。しかし、Three.jsのPLYLoaderで読み込んでも正しく表示されません。 通常のPLYファイルと3DGS PLYファイルの違い 通常のPLY(メッシュ/点群): element vertex 1000 property float x property float y property float z property uchar red property uchar green property uchar blue element face 500 property list uchar int vertex_indices 3DGS用PLY: element vertex 142000 property float x property float y property float z property float f_dc_0 # 球面調和関数(色) property float f_dc_1 property float f_dc_2 property float opacity # 不透明度 property float scale_0 # スケール(楕円体サイズ) property float scale_1 property float scale_2 property float rot_0 # 回転(クォータニオン) property float rot_1 property float rot_2 property float rot_3 PLYLoaderで読み込むとどうなるか Three.jsのPLYLoaderは3DGS用のプロパティを理解できません。読み込むと以下のような問題が発生します: ...