DHフェス2022 が発表者(2/14締切)・参加者募集中です

2022年2月23日の13:00時から開催される、DHフェス2022 というイベントが発表者・参加者を募集しています。

sites.google.com

このイベントは「人文学+デジタルな取り組みを気楽に話しましょう!」という気楽な会合で、 少し前に開催されたイベント、「言語学フェス2022」に インスパイアされたものです。

言語学フェスにならって、oVice というWebブラウザベースのコミュニケーションツールを 用います。ここでは、自分の話をみんなに一度に聞かせることはできないのですが、 代わりに、一つの空間でたくさんの人達がそれぞれに話の輪を自由に作って個別に対話できます。

デジタル・ヒューマニティーズに限らず、人文学においてデジタル技術を活用してみることについて 気楽に誰かに話を聞いてもらったり相談したりすることを目指す会ですので、特に肩肘を張らずに、気楽に 発表申し込みや参加申し込みをしていただければ幸いです。

日本学術会議の公開シンポジウムで人文・社会科学のデジタル研究基盤がテーマとなります

今度の土曜日、1/22に、日本学術会議の公開シンポジウム「総合知創出に向けた人文・社会科学のデジタル研究基盤構築の現在」が開催されます。 日本学術会議には「分野別委員会」があり、それぞれの委員会が分科会を設置して特定のテーマについて議論します。多くの分科会は 1つの分野別委員会の下で活動をしますが、今期は、心理学・教育学委員会、言語・文学委員会、哲学委員会、社会学委員会、史学委員会、地域研究委員会、情報学委員会の 7つの委員会が合同で「デジタル時代における新しい人文・社会科学に関する分科会」を設置して、デジタル・ヒューマニティーズやデジタル技術を用いた社会科学の現状と課題についての議論を行っています。その活動の一環として開催されるのが、1/22の公開シンポジウムということになります。

プログラムは以下のようになっており、人文・社会科学、なかでも、これまであまり採り上げられてこなかった質的研究に関わる研究データの構築とそのための基盤に関する事柄が議論の基底を成す形になっています。

公開シンポジウム「総合知創出に向けた人文・社会科学のデジタル研究基盤構築の現在」|日本学術会議 www.scj.go.jp

人文・社会科学全般において今後重要になっていくテーマの一つであり、また、昨今注目を集めつつある研究データ、オープンサイエンス、研究DXといった様々なテーマを含んでいます。こういった方面に興味がおありの方は、ぜひご参加ください。

Vue.jsで簡単地図マッピング - その2 マーカー表示編

さて、前回記事に引き続き、 Vue.jsで簡単地図マッピングです。

マーカーの地図上での表示

今度は、マーカーを表示してみましょう。

すでにここまでインストールしたモジュールでマーカーの表示はできますので、あとは タグやスクリプトを書いていけば…というところなのですが、一つ注意点があります。 どうやらこのLeafletには少しバグがあるらしくて、マーカーの画像がうまく表示されません。 そこで、

my-app-test/src/main.js

というファイルに、以下のものを追記します。

import L from 'leaflet';
delete L.Icon.Default.prototype._getIconUrl;

L.Icon.Default.mergeOptions({
  iconRetinaUrl: require('leaflet/dist/images/marker-icon-2x.png'),
  iconUrl: require('leaflet/dist/images/marker-icon.png'),
  shadowUrl: require('leaflet/dist/images/marker-shadow.png')
});

筆者の main.js は、全体としては以下のようになっています。

さて、マーカーを表示するための準備が整いましたので、次に、App.vueの方に戻りましょう。 <v-main></v-main>内に<template>~</template>を以下のように追記します。この部分が マーカーとそのポップアップを記述するためのタグ部分です。(ここでは、最後に残った「hello world」も消してしまいました。)

    <v-main>
      <v-btn @click="testfunc">Push</v-btn>
      <l-map ref="map" style="height: 600px;" :zoom="zoom" :center="center">
      <l-tile-layer :url="url"></l-tile-layer>
      <template>
        <l-marker v-for="(marker,index) in markerPlaces"
        :key="marker.id+index"
        :lat-lng="marker.latlon">
          <l-popup>
            <div class="primary--text">{{marker.name}}</div>
            <div v-for="(title, index) in marker.titles"
            :key="'tit-'+index">
              <a :href="title.uri" target="_blank">{{title.title}}</a>
            </div>
          </l-popup>  
        </l-marker>
      </template>          
    </l-map>
    <ul v-for="eachdata in testdata" :key="eachdata.id">
      <li>{{ eachdata.name }}</li>
    </ul>
    </v-main>

ここでも v-for が出てきていることに注目してください。この場合、マーカーのデータが複数あればマーカーを複数プロットできるようにしています。

次に、これに対応すべく、importの箇所も少し追記修正する必要があります。今回のApp.vueのimportの箇所は 全体としては以下のようになっていれば大丈夫です。

<script>
import 'leaflet/dist/leaflet.css'
import { latLng } from "leaflet";
import { LMap, LTileLayer, LMarker, LPopup } from "vue2-leaflet";

要するに、Leafletのコンポーネントをいくつか追加しています。

それから、これに対応するために、componentsの箇所にも追記します。全体としては以下のようになります。

export default {
  name: 'App',
  components: {
    LMap, LTileLayer,
    LMarker,
    LPopup,
  },

さらに、 data: () => ({ }) のところも追記する必要がありますね。 とりあえず、自動的に書くのはちょっとはやいので、以下のように、まずは手動でマーカー二つ分を書いてみます。

  data: () => ({
    url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
    zoom: 8,
    center: [34, 137],
    testdata:[],
    markerPlaces:[
      {name:'ここ',latlon:latLng(35,135),
      titles:{title:'タイトル',
        uri:'https://www.google.com/'},id:1
      }, 
      {name:'あそこ',latlon:latLng(35,136),
      titles:{title:'タイトル2',
        uri:'https://www.google.com/'},id:2
      }
    ],
  }),

これは、今回の、<template>内に書き込んだ<l-marker v-for="(marker,index) in markerPlaces" 以下の箇所に対応したものです。 このようにしてdata: () => ({ のところにデータを書き込んでおくと、Web頁読み込み時に表示してくれます。

というわけで、うまくいっていると、以下のようにマーカーが2カ所表示されたマップが表示されるはずです。

マーカーの複数表示

ここまでくればもう大丈夫、という人も多いと思いますが、念のためもう少しだけやっておきましょう。 Pushを押すと別の箇所にマーカーをいくつか表示してくれるものを作ってみます。

これはもう、testfunc関数を書き換えてやってしまいましょう。以下のように書けばOKです。 西から東へ1度ずつずらして5つのマーカーを表示させるように、 test.markerPlacesに値を 入れていく処理です。

なお、現在のマーカーの数もtest.markerPlacesの要素数を数えれば確認できますが、 今回は、最初に2つのマーカーを表示していますし、繰り返し処理のときは5つ表示しますので、 if (this.markerPlaces.length < 3){という風に、3未満かどうか、で判定しています。

      if (this.markerPlaces.length < 3){
        let datalist = []
        let datatext = {}
        let titdict = {}
        let lon = 134
        for (let n=0;n<5;n++){
          datatext['id'] = n
          datatext['name'] = '名前:'+n
          datatext['latlon'] = latLng(35,lon+n)
          titdict = {}
          titdict['title'] = 'タイトル'+n
          titdict['uri'] = 'https://www.google.com/'
          datatext['titles'] = []
          datatext['titles'].push(titdict)
          datalist.push(datatext)
          datatext = {}
        }
        this.markerPlaces = datalist
      }
      else{
        this.markerPlaces = []
      }

これがうまくいくと、以下のように、Pushボタンを押した時に東西に5つのマーカーが並ぶことになります。 マーカーをクリックするとポップアップが表示されることも確認できますね。また、 グーグルへのリンクも表示されているはずです。

というような感じで、地図上に複数のマーカーをプロットできました。 あとは、データを増やしたければ、外部のJSONデータを うまく取り込むなどしていただくとよいのですが、それもまたもう少ししたらご紹介しましょう。

公開するために:静的なファイル群に変換

最後に、このようにして作ったものを、HTML/Javascript/CSSファイルとして公開できる形にしてみましょう。

ここまで、 $ npm run serve というコマンドを打って以下のような状態になっている ウインドウがあるはずです。

このウインドウを、 Ctrl-c で抜けて、コマンドプロンプトに戻ってから、

$ npm run build

としてください。そうしますと、これも少し時間がかかりますが、処理が終わると、 このディレクトリに 「 dist 」というフォルダができていて、その中に index.html や、いくつかのディレクトリができているはずです。この dist の中にあるファイルを すべてまとめてサーバにアップロードすると、それが使えるようになります。

それだけでなく、これは面白いことに、サーバを立ち上げなくても、 パソコン上の単なるHTMLファイルとしても動作してくれます。エクスプローラーで この dist フォルダの中にある index.html をWebブラウザで開いてみてください。 そうすると、先ほどと同じように開いて、Pushボタンを押すと同じように 5つのマーカーが表示されるはずです。Vue.jsで書く場合、基本的に クライアント側(パソコン側)のJavascriptしか使わないので、このように、 パソコン単体で動くことを利用して、パソコン用アプリケーションを 作ることも可能です。

ここまで、原理的な解説はほとんどせずに進んできてしまいましたが、 Vue.jsの公式サイトやあちこちのブログなどで Vue.jsの解説は書いてありますので ここは筆者が敢えて書くまでもないと思います。Webで色々見ていただければと思います。 (ではなぜ、ここまで、どこにでも書いてありそうなことをわざわざ書いている のかと言われそうですが、実は、ここまで一貫して書いてある良い記事になかなか 巡り会えず、あちこちの記事や公式サイトの記述を参照しながらなんとか 書くことができたので、この手のことをやってみたい方々が同じ苦労(かなりムダが多いので)を しなくて済むように、その経験をまとめているのがこの記事になります。)

Vue.jsで簡単地図マッピング - その1 準備編(2021/12/13追記)

前回はTEIファイルから地図マッピングをする話でしたが、今回は少し違う角度から取り組んでみます。

最近、JDCatデータのお試し検索サイトというものを作ってみました。

人文社会科学の研究データを総欄できるサイトとして最近運用が始まった JDCatというサイトがありますが、 こちらで集約して検索できるようにしているメタデータはCC0で公開されていますので、 せっかくのCC0を活かして教材作り等に使えないかということで試しに作ってみたのが 上記のお試し検索サイトです。ちなみにソースコードはこちらですが、 ファセット検索の部分がお手製コードなので非常に微妙ですのであまり じっくりみないでください…。通常はここは、ElasticsearchとかApache Solr等で 検索して戻ってきたファセットのデータを使うところを、検索自体を Javascriptの中でやってしまっているので、ファセットの処理も 自分で書いてしまっています。もっとスマートな書き方があると思いますが ご容赦ください…

何の教材かと言えば、vue.jsの使い方です。vue.jsで何度かサイトを構築してみて まあまあわかってきたということと、これから若者にJavascriptの勉強を始めてもらうなら vue.jsかreactだろうということで、とりあえずは vue.jsです。

ただし、vue.jsだけだとデザイン的にはあまりこじゃれた感じにはならないのですが、 ここで出てくるのがveutifyです。これに従ってタグと スクリプトを書けば、非常に少ないコードでとてもきれいで動的なサイトを 構築できます。

開発環境の構築

ということで、まずはvuetifyの導入からです。これにはいくつかの段階が必要で、 しかも、色々と前提条件を理解した方が本来は良いのですが、最初にここから 入る人もこれからは多いのでしょうから、 「やってみたらできた」というところをとりあえずは目指していきましょう。

vue.jsは、npmというパッケージシステムから使うのが便利(?)です。 もっと良い方法も最近はあるかもしれませんが、とりあえず、比較的枯れたやりかたでも ありますので、これでいきましょう。

筆者の環境は、Windows 10 にWSL2を入れてそこでUbuntu18 を動かし、Ubuntu 18 に npm をインストールしています。MacOSの場合は、何もせずともそのまま 「ターミナル」を開くだけでよいと思います。Windows10/11の場合は、 Ubuntu18を入れるところまでは、他のサイトで確認してください。Ubuntu20でも 多分同様にできると思いますが、試していないのでわかりません。

Ubuntu18 を入れると「ターミナル」でUbuntu18を操作できるようになります。 そこで、以下のようにコマンドを入力してエンターキーを押します。

$ sudo apt install npm

そうすると、npmというコマンドが使えるようになります。

その後いくつかのコマンドを入力すると、Vuetifyが使えるようになります。 大体、以下のような感じでいけると思います。

$ sudo npm install vue $ sudo npm install -g @vue/cli @vue/cli-service-global

ここまできたら、次はこちらのサイトに従って操作すれば、多分以下のコマンドで自分のプロジェクトを立ち上げることができると思います。

$ vue create my-app-test ※ my-app-test のところには自分のプロジェクト名を書いてください。(my-app-test のままでも大丈夫です) ※何か色々聞かれますが、全部そのままリターン(エンター)を押せば大丈夫です。

$ cd my-app-test ※前のコマンドで my-app-test を別の文字列にした場合は、それと同じものをこのコマンドの my-app-test のところに書く

とりあえずここまできたら、次は以下のコマンドを入力して、動作確認をしてみます。 $ npm run serve

上記のコマンドを打った後は、少し時間がかかりますが、以下のような画面になるはずです。

そうしましたら、指示されている localhost の方のURL ( http://localhost:8080/ 等 )にWebブラウザでアクセスすると 以下のような頁が表示されるはずです。

ここまでできたら、Vue.jsのインストールは成功です。次に、Vuetifyをインストールしますので、 コマンドラインに戻ってCtrl - C を押して、コマンドプロンプトに戻ってください。

次に、以下のコマンドを打ってください。

$ vue add vuetify

これで、vuetify を使えるようになったはずです。 もう一度、以下のコマンドを実行してから指定されたURL ( http://localhost:8080/ 等)にアクセスすると、

$ npm run serve

今度は以下のような頁が表示されるはずです。なかなかかっこいい頁ですね。これで準備はOKです。

現在は、 my-app-test フォルダ(ディレクトリ)の中にいるはずですので、現在のフォルダ内の ファイルをウインドウズのエクスプローラー等で一覧してみますと、以下のようになるはずです。

ここに入っているファイル群にプログラムを書いたりモジュールを追加したりすることで、 Vue.js / Vuetifyを活用したサイトを構築できるということになります。 なお、一定の制約はありますが、基本的にここで動くモノはローカルパソコンでも動きます ので、スタンドアロンアプリのようにして使うこともできます。

それではいよいよ、vuetifyを使ったWeb頁の作成です。上記のフォルダの中の 「src」というフォルダを開いてみてください。そうすると以下のような ファイル一覧が表示されるはずです。ここで、「App.vue」というファイルを 編集することになります。編集には、VSCodeの利用をおすすめします。

さて、ここでまず、後々の作業を少しだけ楽にするための設定をしておきます。 このファイル一覧にある vue.config.js というファイルをVSCodeやメモ帳等のテキストエディタで開くと以下のように 書き込まれているはずです。

module.exports = {
  transpileDependencies: [
    'vuetify'
  ]
}

これに追記をして、以下のようにして保存してください。

module.exports = {
  transpileDependencies: [
    'vuetify'
  ],
  configureWebpack: {
    devServer: {
      watchOptions: {
    ignored: /node_modules/,
        poll: true
      }
    }
  },
  publicPath: './'
}

それから、一度 npm run serve をCtrl-c で停止して、もう一度実行してください。 これで、「スクリプトを書き換えると http://localhost:8080/ で開いているWeb頁 がそれにあわせて変更される」ようになりました。

さて、ではさっそく、VSCodeで App.vueを開いて書き換えを試してみましょう。

以下のように、<HelloWorld/>というタグが書かれた箇所がありますので、その一行上に hello world と書き込んで保存してから、 http://localhost:8080/ をみてみましょう。

    <v-main>
      hello world
      <HelloWorld/>
    </v-main>
  </v-app>
</template>

そうすると、以下のような感じで「hello world」が 表示されるはずです。このようにして、App.vueを書き換えると自動的に頁が更新されることになります。 これはなかなか便利ですね。

では次に、何か動くものを書いてみましょう。

動くものを少し書いてみる

まずは、 「ボタンを押すと表示される」

というものを書いてみたいと思います。 普通にファイルを開くと40行目あたりからになりますが、 以下のように書いてみます。スクリプトの下に解説を少し書いておきますので ご参考にしてください。

    <v-main>
      hello world
      <HelloWorld/>
      <v-btn @click="testfunc">Push</v-btn>
      <div>{{ testdata }}</div>

    </v-main>
  </v-app>
</template>

<script>
import HelloWorld from './components/HelloWorld';

export default {
  name: 'App',

  components: {
    HelloWorld,
  },

  data: () => ({
    testdata:'',
  }),
  methods:{
    testfunc:function(){
      this.testdata = 'This is a test.'
      
    }
  }

上記のスクリプトを少し解説しますと、まず、 <v-main>というタグの中で、<HelloWorld/>の次の行に、 ボタン表示するタグを書き (<v-btn></v-btn>)、そこで、クリックするとtestfuncという 関数が動作するように(@click="testfunc")、タグの属性の位置に書いておきます。 それから、次の行には、結果表示のために<div></div>タグを用意した上で、 そこに、変数testdataの中身が表示されるように{{ test data }} と書いておきます。ここでは 「{{ }} 」がキモです。

次に、変数 testdata をこのタグの中で利用できるように、「 data: () => ({」以下に、 「testdata:'', 」と書き込んでおきます。これは、testdataの変数の型がテキストであることを示しています。

そして最後に、data:() =>({...}), の次に、カンマで区切った後にmethods:{}を書き込み、 さらにそのなかに、関数testfunc を書き込みます。今回は単に、'This is a test.'と 表示するだけのスクリプトを書いてみますが、そのためには、 this.testdata に表示したい 文字列を代入することになります。つまり「this.testdata = 'This is a test.'」ということですね。 これができたら保存して、「Push」ボタンがWeb頁上に表示されたらクリックしてみてください。

それで、上記のようにボタンの下に「This is a test.」と表示されれば成功です。

次に、ボタンを押すたびに表示/非表示を切り替えるようにしてみましょう。 testfunc(){ } の中に以下のようなスクリプトを書いてWeb頁上で表示/非表示を切り替えてみてください。

    testfunc:function(){
      if (this.testdata == ''){
        this.testdata = 'This is a test.'
      }
      else{
        this.testdata = ''
      }
    }

これは、this.testdata の内容を判定して表示したり表示しなかったりという処理をしていますね。 これがうまくいけば、とりあえずVuetifyの使い方の基本中の基本はできたはずです。

地図を表示

次に、色々端折って、地図を表示してみましょう。これは前回記事と同じLeafletですが、vue.jsを使っている 今回の環境の場合とは、インストールの仕方も設定の仕方も異なります。

まずは、コマンドラインに戻って、以下のコマンドを実行して vue2 用のleafletをインストールしてみましょう。

$ npm install vue2-leaflet leaflet --save

やや拍子抜けですが、これでインストール終了です。

では次に Leafletの地図の表示ですが、必要な情報を今までのスクリプトに追記したものの該当箇所は以下のようになります。地図は ぐりぐり動かせますのでぜひ試してみてください。

    <v-main>
      hello world
      <HelloWorld/>
      <v-btn @click="testfunc">Push</v-btn>
      <div>{{ testdata }}</div>
      <l-map ref="map" style="height: 600px;" :zoom="zoom" :center="center">
      <l-tile-layer :url="url"></l-tile-layer>
    </l-map>
    </v-main>
  </v-app>
</template>

<script>
import HelloWorld from './components/HelloWorld';
import 'leaflet/dist/leaflet.css'
import { LMap, LTileLayer } from "vue2-leaflet";

export default {
  name: 'App',
  components: {
    HelloWorld,
    LMap, LTileLayer
  },
  data: () => ({
    url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
    zoom: 8,
    center: [34, 137],
    testdata:'',
  }),

上記のスクリプトを簡単に解説すると、まず templateタグ内の<v-main>の中に<l-map>...</l-map>として地図の位置と高さなどの 情報を書き込んでおきます。

次に、LeafletのCSSと vue2-leafletモジュールを import します。

そして最後に、 data: () => ({のところで、url:とzoom: と center:のデータをそれぞれ書いています。

これで、地図が表示できるようになりましたが、さらに、先ほどの Pushボタンや VuetifyのWelcome画面がまだ残っていますね。 とりあえず、Vuetify のWelcome画面だけは消してしまいましょう。そのためには、3カ所、削除する必要がある場所があります。 どれかを残してしまうと、Vueは「不整合だ」といってエラーを表示してくれますので、3つとも削除します。 まずは、<HelloWorld/> タグ、それから、「import HelloWorld from './components/HelloWorld';」の行、 それに加えて、「 components: { 」の次の行にある「HelloWorld,」です。これらを削除すると以下のようになりますね。

    <v-main>
      hello world
      
      <v-btn @click="testfunc">Push</v-btn>
      <div>{{ testdata }}</div>
      <l-map ref="map" style="height: 600px;" :zoom="zoom" :center="center">
      <l-tile-layer :url="url"></l-tile-layer>
    </l-map>
    </v-main>
  </v-app>
</template>

<script>

import 'leaflet/dist/leaflet.css'
import { LMap, LTileLayer } from "vue2-leaflet";

export default {
  name: 'App',
  components: {

    LMap, LTileLayer
  },
  data: () => ({
    url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
    zoom: 8,
    center: [34, 137],
    testdata:'',
  }),

これで、以下のように、Pushボタンやテキストの「hello world」を残しつつ、その直下に地図が表示されるはずです。

だいぶいい感じになってきましたね。

v-forを試してみる

次に、仮想DOMを象徴する便利な書き方であるところの v-for を試してみます。これは、たとえば 複数マーカーを地図上に簡単に表示するために有用なものですが、応用性が高いので、 その基本的な書き方のみをまずはみてみましょう。

ちょっと長いですが、以下のように追記をしてみます。

    </l-map>
    <ul v-for="eachdata in testdata" :key="eachdata.id">
      <li>{{ eachdata.name }}</li>
    </ul>
    </v-main>
  </v-app>
</template>

<script>

import 'leaflet/dist/leaflet.css'
import { LMap, LTileLayer } from "vue2-leaflet";

export default {
  name: 'App',
  components: {

    LMap, LTileLayer
  },
  data: () => ({
    url: "http://{s}.tile.osm.org/{z}/{x}/{y}.png",
    zoom: 8,
    center: [34, 137],
    testdata:[],
  }),
  methods:{
    testfunc:function(){
      if (this.testdata.length == 0){
        let datalist = []
        let datatext = {}
        for (let n=0;n<5;n++){
          datatext['id'] = n
          datatext['name'] = 'name:'+n
          datalist.push(datatext)
          datatext = {}
        }
        this.testdata = datalist
      }
      else{
        this.testdata = []
      }
    }
  }
};
</script>

追記をしているのは、まずは <template></template>の中の以下のタグです。v-forを用いることで 配下のタグ等に関して繰り返し処理ができるようになります。

   <ul v-for="eachdata in testdata" :key="eachdata.id">
      <li>{{ eachdata.name }}</li>
    </ul>

次に、date: ()の中に書き込む事項です。ここでは、これまでテキスト型であったtestdata:を 配列型に書き換えています。「testdata: []」として配列型で初期化しています。

それから、「if (this.testdata.length == 0){ 」の行の後に、今回の処理を行うための処理を書き込みます。 ここでは、v-forに書き込むためのデータを用意しています。v-forのおそろしく面白いところは、 Javascriptで構築した配列やObject(Pythonで言うdictとかPHPで言う連想配列と考えてください)を、 繰り返しも含めてHTMLに表現できてしまうところです。…といっても何を言っているのかよくわからない かもしれませんので、この例を用意しています。

少し上に戻って、<ul v-for="eachdata in testdata" :key="eachdata.id">のタグをみてみると、 testdata という変数(これはlist)から eachdata を取り出す、という処理をしており、さらに、 :key に対して、eachdata.id としてeachdata Objectのidキーをあてていることになります。 さらに次の行 <li>{{ eachdata.name }}</li> を見ると、 {{ }} の中に、eachdata Objectのnameキーを 記述しています。つまり、

this.testdata = [{"id:"01","name":"good"},{"id:"02","name":"wonderful"},{"id:"03","name":"excellent"}]

というような感じの、Objectを要素とする配列をthis.testdatadata: () => {以下にある変数 testdataを指している)に入れれば、それが上記の v-for属性のついたタグのところに 入力されることになります。それを、一つずつ書くのではなく、とりあえず for でまわしてデータを 構築しているのが、以下の箇所です。

      if (this.testdata.length == 0){
        let datalist = []
        let datatext = {}
        for (let n=0;n<5;n++){
          datatext['id'] = n
          datatext['name'] = 'name:'+n
          datalist.push(datatext)
          datatext = {}
        }
        this.testdata = datalist
      }
      else{
        this.testdata = []
      }
    }

ここでは、もしthis.testdataが空ならば(空であることを判定するために、this.testdata.length で、この配列内に要素が存在するかどうかを判定しています)、 datatextというオブジェクトを作って一つずつ配列 this.testdataに入れるという操作を5回繰り返し、一方、もし空でなければ、this.testdata を空にする(ただし これは配列なので空にするためには [] を用いる)、という処理をしています。この一連の処理を、ボタンを押す=testfunc関数がclickで動作するたびに 行う、というのがこの処理です。これをうまく書けると、以下のように、Pushボタンを押すたびに5行のアイテムが表示されたり消えたりを繰り返します。

というわけで、やや長くなってしまいましたので一旦ここら辺で区切ります。次回はまた近日中に、ということで よろしくお願いいたします。

Digital 法寶義林 (Hôbôgirin) の作り方/TEIファイルから地図年表マッピング-その1

先週の土曜日、人文情報学による仏教知識構造化の新潮流 ​というシンポジウムがあり、 そこでDigital 法寶義林 (Hôbôgirin) というサイトが公開されました。このサイトは インターフェイスをJavascriptで作り込んでおり、インターフェイスからサーバ側にリクエストがあると、 サーバ側ではTEI/XMLで書かれたデータ(ファイル)をJSONに変換して返戻し、インターフェイス(Javascript)側では それを地図や年表などにプロットする、という風になっています。全体的にはよくある構成ですが、 サーバ側に置いてあるのがTEI/XMLファイルである、という点が、よくあるやり方とはちょっと違っています。

基本的に、サーバ側でデータを扱う時は、データサイズが大きくなっても大丈夫なように、検索に特化された ソフトウェアを使うのが一般的です。これも用途やデータ形式によっていくつか選択肢があり、それぞれ フリーソフトウェアで公開されていますが、たとえば、サーバ上でデータ更新まですることを 前提に表形式のデータや表を組み合わせた形式のデータを用いるのであれば、 伝統的にはリレーショナルデータベースシステム(RDBMS)であるPostgreSQLやMySQLを用いる ことが多いように思います。 検索に特化する上にデータ量が多い場合には、Apache Luceneというフリーの全文検索ライブラリ があり、これを組み込んだ全文検索ソフトウェアとしてApache SolrやElasticsearchなどがよく 用いられます。特にElasticsearchは、ジャパンサーチやWeko3などの公的なものでも利用されて おり、最近はとくに流行っているようです。数百万件のデータでも一瞬で検索できます。

しかしながら一方で、最近はコンピュータもかなり速くなってしまい、テキストデータでも 10MBくらいのデータなら一瞬で処理できてしまいます。そこで、サーバ側でデータ更新をすることが なく、それほど大きくない上に急激にデータ量が増える見通しもない場合には、CSVやTSV、あるいはJSONやXMLのファイルを そのままプログラムで開いて処理してしまうことも十分に選択肢に入ってきます。 今回のDigital 法寶義林では、そのような構成(サーバ側ではPHPでTEI/XMLファイルを開いて処理)に なっています。PHPにはSimpleXMLというモジュールが用意されており、これを用いると 簡単にXMLをパース(XMLのルールに沿って読み込んで処理できるようにする)できます。 ただ、サーバ側のプログラムは別にPHPである必要はなく、むしろ、Python3のBeautifulsoupという モジュールの方がPHPのSimpleXMLよりもずっと使いやすいです。

というわけで、データの取り出し方ですが、今回のDigital 法寶義林のデータ(注:4MBあります)をみてみると、 普通のTEI/XMLですが、全体として、本文部分(<body>)と後付(<back>)に分かれていることがまずはみてとれます。

Oxygen XML Editorで<body>タグと<back>タグ等を畳んでみたところ

それぞれのタグには3万行以上が含まれているようで、もう眼で確認するには難しい分量ですが、とりあえず本文部分をもう少しみて みますと、<listPerson>というタグが入っています。

<body>の中に<listPerson>がある

一方、後付の中には、以下のように、いくつかの <list...>タグが含まれています。

後付に含まれるリスト系タグ群

つまり、このデータでは、参照用に用いられるデータを<back>にリストして、そこにリストされたデータを本文の人名情報から参照する、という形になっています。 このうちの<listPlace>には、type="place"とついているものとtype="vih"とついているものがあります。これは、type属性を用いて、地名情報を、一般的な地名と寺院名に分けています。そこで、寺院の地名情報を開いてみていると以下のようになっています。

寺院の地名情報

特に <place xml:id="vh0001"> 以下に注目してみましょう。ここではまず、地名の表記の仕方を<placeName>で列挙しつつxml:lang属性で区別しています。 次に実際の場所を<locaton>の中の<geo>タグの中に座標データとして記述しています。座標情報は、人によって判断が異なることもあるため、誰に責任があるかということをresp属性で示し、さらに、情報源がある場合はsource属性で示しています。この、respやsourceといった属性の中身は、地図上にこのお寺をマッピングする際にはまったく不要な情報ですが、しかしこの情報の根拠を確認したくなったときには重要です。TEI/XMLのファイルでは、このようにして、すぐには使用しなくても書いておきたい情報をとりあえず記述して流通させることができます。

さて、このような感じのデータですので、とりあえず寺院の情報を地図上にマッピングしてみることを考えてみましょう。

地図上にマッピングする方法概観

まず、地図上にマッピングするデータを作成します。これは、上記のように、TEI/XMLファイルからマッピング用のデータを抽出するプログラム等を作成すれば大丈夫です。このプログラムは比較的簡単なものです。検索してデータを絞り込む等の機能をプログラムに追加しておくと、より便利になるでしょう。

次に、地図上にマッピングするのですから、地図を使えるようにする必要があります。地図上にマッピングする際に簡便に使えるのは、Leaflet という Javascriptのライブラリです。お金をかけずに済ませようとするなら、これに OpenStreetMapを組み合わせるのがおすすめです。これを用いて、Webブラウザ上で地図を表示してぐりぐり動かせるようにします。

その上で、この地図に、TEI/XMLファイルから取り出したデータをマッピングすることになります。

地図上にマッピングしてみる:データ抽出編

では、まずはデータの作成です。TEI/XMLファイルから抽出する、と言われても何をどうしたらいいのか…という人もいれば、すぐにできる、という人もいるでしょう。 とりあえず、Python3がある、という前提でやってみましょうか。

Python3では、XMLファイルを簡単に処理するための便利なモジュールとして Beautifulsoupがあります。とりあえずこれをインストールすると、目当てのタグ/属性でデータを抽出できるようになります。Python3(dictの順序が一定になるバージョン3.6以降をおすすめします)をインストールした環境で

$ sudo pip3 install bs4

などとするとBeautifulsoupが使えるようになります。

ここで、たとえば、以下のように書くと、登録されている寺院の名前だけを取り出すことができます。

#!/usr/bin/env python3
from bs4 import BeautifulSoup
import sys
fname = sys.argv[1]
with open(fname , encoding='utf-8') as f:
    all_data = BeautifulSoup(f, 'xml')
    listPlaces = all_data.select('listPlace[type="vih"]')
    for places in listPlaces:
        place = places.select('place')
        for placeInfo in place:
            placeNames = placeInfo.select('placeName[xml\:lang="zh"]')
            for pl in placeNames:
                print (pl.get_text().strip())

(ここではわかりやすくするため、CSSセレクタの記法と近い .select() を使用しています) ここで何をしているのかと言えば、寺院の名前は <listPlace type="vih">の中にエレメントとしてリストされているので、 まずはそれを取り出します。(listPlacesに入れる)。それから、listPlacesの中にある<place>を取り出して、 さらにその中にあるplaceNameのうち、漢字で書かれたもの(ここではxml:lang="zh"が付与されている)を 取り出して、 .get_text() を用いて取り出して表示する、という処理を行っています。前後に空白がついている ことがあるのでそれを削除するために .strip() も用いています。

このスクリプトを extract.py としておいて、以下のようにすれば寺院名がリストされるはずです。

$ python3 extract.py hobogirin_tei.xml

このようにして地名を取り出すことができました。次に、座標情報がほしいですね。これは、以下のようにして 取り出して寺院名と交互に表示できます。

#!/usr/bin/env python3
from bs4 import BeautifulSoup
import sys
fname = sys.argv[1]
with open(fname , encoding='utf-8') as f:
    all_data = BeautifulSoup(f, 'xml')
    listPlaces = all_data.select('listPlace[type="vih"]')
    for places in listPlaces:
        place = places.select('place')
        for placeInfo in place:
            placeNames = placeInfo.select('placeName[xml\:lang="zh"]')
            for pl in placeNames:
                print (pl.get_text().strip())
            #以下、追記部分
            locations = placeInfo.select('location')
            for location in locations:
                geos = location.select('geo')
                for geo in geos:
                    print (geo.get_text())

さて、このようにして取り出せるとなると、次はこれをJavascriptで扱いやすいようにJSONに変換したくなってしまいますね。そこでjsonモジュールを導入です。 …といっても、jsonモジュールは標準で入っているようですのでわざわざインストールする必要はなさそうです。

そこで、このデータをdictとlistを使ってさくっとJSON形式にしてみましょう。

#!/usr/bin/env python3
from bs4 import BeautifulSoup
import sys
import json
# jsonモジュールを読み込み
fname = sys.argv[1]
temple_list = []
# この配列↑に、JSON形式の抽出結果に入れるべき寺院のデータを追加すべく1回だけ初期化
with open(fname , encoding='utf-8') as f:
    all_data = BeautifulSoup(f, 'xml')
    listPlaces = all_data.select('listPlace[type="vih"]')
    for places in listPlaces:
        place = places.select('place')
        for placeInfo in place:
            temple_dict = {}
            #この辞書↑に個々の寺院のデータ(名前と座標)を入れるべく毎回初期化
            placeNames = placeInfo.select('placeName[xml\:lang="zh"]')
            for pl in placeNames:
                temple_dict['temple'] = pl.get_text().strip()
                #先ほどはただ出力していた寺院名をtemp_dict辞書に"temple"をキーとして格納
            locations = placeInfo.select('location')
            for location in locations:
                geos = location.select('geo')
                for geo in geos:
                    geolist = geo.get_text().strip().split(',')
                    #座標情報のカンマで区切られた緯度経度データをカンマで区切ってそれぞれ配列の要素としてgeolist配列に格納
                    temple_dict["lat"] = geolist[0].strip()
                    temple_dict["lon"] = geolist[1].strip()
                    # temp_dict辞書に緯度経度をそれぞれ格納。
            temple_list.append(temple_dict)
            # temp_list配列にtemp_dict辞書=寺院の情報を追加

print ('var temples = '+json.dumps(temple_list, indent=2, ensure_ascii=False))
# 最後に、すべてのデータをtemp_listに入れ終わったら json_dumpsでJSON形式にして出力

というような感じで、とりあえず漢字の寺院名と座標情報をJSON形式で出力できるようになります。これをリダイレクトで 出力して、静的なJSONファイルを作っておきましょう。たとえば以下のような感じです。

$ python3  extract.py hobogirin_tei.xml > all_temples.js

このall_temples.jsというファイルを、Javascriptの地図に読み込めるようにするのが次のステップです。

なお、同様にして、<listPlace type="place">をターゲットにすると、寺院名ではなく地名の情報がとれます。 また、placeInfo.select('placeName[xml\:lang="zh"]')zhを他の言語タグに切り替えればその言語タグの寺院名を取得できます。 データを見ながら色々試してみると面白いかもしれません。

地図上にマッピングしてみる:地図表示編

さて、次に、地図をWebブラウザ上に表示してみます。これは、上述のようにLeafletを使うのですが、Web上で参照可能なライブラリ として公開されていますので、それを使ってさくっと作ってしまいます。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="format-detection" content="telephone=no">
<title>タイトル</title>
<meta name="description" content="test for mapping">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<style type="text/css">
    <!--
     #leafletmapid { height: 400px; width: 600px}
   -->
   </style>
</head>
<body>
<h1>test for mapping</h1>
    
<div id="leafletmapid"></div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
   integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
   crossorigin=""></script>
<script>
   var mymapt = L.map('leafletmapid').setView([34.058, 115.548], 4);
   var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>', maxZoom: 19});
     tileLayer.addTo(mymapt);
    var marker = L.marker([35.7101, 139.8107]).addTo(mymapt);
</script>
</body>
</html>

これを test.html 等のファイル名で保存して、ネットにつながったパソコン上でGoogle Chrome等で開くと、Leafletで表示したOpenStreetMap上にマーカーが一つプロットされていると思います。以下のような感じですね。

ここに、先ほどのJSONデータを読み込ませたいのですが…とりあえずちょっとやってみましょう。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="format-detection" content="telephone=no">
<title>タイトル</title>
<meta name="description" content="test for mapping">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<style type="text/css">
    <!--
     #leafletmapid { height: 400px; width: 600px}
   -->
   </style>
</head>
<body>
<h1>test for mapping</h1>
    
<div id="leafletmapid"></div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
   integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
   crossorigin=""></script>
<script src="all_temples.js"></script>
<!-- ↑このタグで、先ほど作成したJSONデータを読み込み-->
<script>
    var mymapt = L.map('leafletmapid').setView([34.058, 115.548], 4);
    var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>', maxZoom: 19});
     tileLayer.addTo(mymapt);
    temples.forEach(function(e){
         // ↑ forEachで JSONデータがリストで入っている変数temples内のリスト要素(=寺院情報)を一つずつ変数eに入れて処理。
        if(e.lat != undefined){
            var marker = L.marker([e.lat, e.lon]).addTo(mymapt);
                                          //↑ここの e.lat とe.longでJSONデータ中の緯度経度情報を取り出して読み込ませる 
        }
    });
    // ↑ここのforEach()の繰り返し処理で、寺院のデータを繰り返し処理。
</script>
</body>
</html>

これでうまくいくと以下のようになるはずです。地図を拡大してみると、少し見やすくなると思います。

さて、寺院名が出ないと何が何だかわかりませんね。寺院名はJSONデータに temple というキーで格納してありますから、これをマーカーにポップアップで表示されるようにすればOKです。ちょっと端折りますが、上のHTMLの繰り返し処理のところに以下のように追記します。

    temples.forEach(function(e){
        if(e.lat != undefined){
            var marker = L.marker([e.lat, e.lon]).addTo(mymapt);
            marker.bindPopup(e.temple);
            // ↑ この行を追記。e.templeで変数eからキーtempleの値を取り出して読み込ませる
        }
    });

そうすると、以下のように、マーカーをクリックするとポップアップで表示されます。

さて、これだとやはりちょっと見づらいですね。何が見づらいかって、マーカーが重なりすぎて何がなんだかわかりません。 こういうときは、マーカーをまとめてくれるJavascriptライブラリがありますのでそれを組み込んでみます。以下のような感じにします。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="format-detection" content="telephone=no">
<title>タイトル</title>
<meta name="description" content="test for mapping">
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.3.0/dist/MarkerCluster.css" />
<link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.3.0/dist/MarkerCluster.Default.css" />
<!-- ↑この二つのCSSファイルを追加します。-->
<style type="text/css">
    <!--
     #leafletmapid { height: 400px; width: 600px}
   -->
   </style>
</head>
<body>
<h1>test for mapping</h1>
    
<div id="leafletmapid"></div>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
   integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
   crossorigin=""></script>
<script src="https://unpkg.com/leaflet.markercluster@1.3.0/dist/leaflet.markercluster.js"></script>
<!-- ↑このスクリプトを追加します。-->
<script src="all_temples.js"></script>
<script>
    var mymapt = L.map('leafletmapid').setView([34.058, 115.548], 4);
    var tileLayer = L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{attribution: '© <a href="http://osm.org/copyright">OpenStreetMap</a> contributors, <a href="http://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>', maxZoom: 19});
     tileLayer.addTo(mymapt);
    var markers = L.markerClusterGroup();
    // markersという変数でマーカーをクラスタできるように初期化しておきます
    temples.forEach(function(e){
        if(e.lat != undefined){
            var marker = L.marker([e.lat, e.lon]);
            marker.bindPopup(e.temple);
            markers.addLayer(marker);
            //今回は、markersにmarkerをどんどん追加していきます。
        }
    });
    mymapt.addLayer(markers);
    //すべての寺院情報がmarkersに追加されたら、それを地図に追加します。
</script>
</body>
</html>

うまくいけば、以下のようにしてマーカーがクラスタとして表示されて、地図を拡大すると適宜マーカーが分散表示されるようになります。

というわけで、TEI/XMLのデータを地図上にマッピングして簡単に閲覧できるところまできました。とりあえず今日はこの辺にしておきたいと思います。

増上寺三大蔵がユネスコ「世界の記憶」における国際登録の登録申請案件に

先日、文部科学省から、増上寺三大蔵がユネスコ「世界の記憶」における国際登録の登録申請案件に推薦されることになったとのお知らせがありました。

文部科学省のサイトによれば、ユネスコの「世界の記憶」は、以下のようなもののようです。

世界的に重要な記録物への認識を高め、保存やアクセスを促進することを目的に、ユネスコが1992年に開始した事業の総称。本事業を代表するものとして、人類史において特に重要な記録物を国際的に登録する制度が1995年より実施されている。

そして、この「世界の記憶」の現在の登録状況は、

現時点で429件が国際登録、56件が地域登録されている。日本からは国際登録に7件、地域登録に1件が登録されている。

とのことです。

ここで話題にしている増上寺三大蔵は、この文科省のサイトでは以下のように説明されています。

17 世紀初頭に徳川家康が日本全国から収集し、浄土宗の大本山である増上寺に寄進した、三部の木版印刷の大蔵経(※)。現代の仏教研究の基礎を為すという文化史上はもとより、漢字文化、印刷文化の観点からも貴重な史料。全て国指定重要文化財。 ※「大蔵経」…5,000 巻を超える仏教聖典の叢書。

家康が集めて寄進した、という経緯もなかなかすごい話で、当時はまだ入手がかなり困難であった大蔵経を、3セットも、しかも異なる時代・地域で印刷されたものを集めたというのもすごいことですが、これに加えて、天海僧正に木活字版の大蔵経を編さんさせたり(このあたりはまだよく理解していなくて、家康は支援していただけ?)もしていたようです。(これは寛永寺で実施されたようで、天海版として現在も各地の古寺名刹の経蔵に納められているそうです。ColBaseで刊記がみられたり国文学研究資料館で妙法蓮華經観世音菩薩普門品の全頁画像が見られたりします。)

この三大蔵(木版印刷で、宋代に刊行された思渓版、元代に刊行された普寧版、高麗で刊行された高麗版再雕本)が現代の仏教研究の基礎を成す、というのは、直接には、明治14年から18年にかけて刊行された大日本校訂大蔵経(縮刷蔵)の校訂編纂にこの三大蔵が使われたからなのです。お寺の宝である大蔵経ですので、おいそれと人に見せられるものでもなく、そもそも分量が膨大ですので(5000巻以上になる)、それを3セット広げて確認するだけでも相当な時間とそれなりの場所を必要とします。日常業務に加えてこれを3セット全部出して、編纂作業をしにくる人たちが扱いやすいように用意する、ということを考えただけでもかなり膨大な作業量になってしまいそうです。実際のところ、お寺が大切に所蔵してきた大蔵経を、単なる複製ではなく編纂に供した例は国内では他にはあんまり思いつきません。獅谷白蓮社の忍澂上人が1702年から5年かけ、若者達を動員して建仁寺の高麗版(現在は焼失)を黄檗版(鉄眼版とも。主に嘉興蔵を模し、日本で広く普及した整版。)と一通り比較した、という話は残っていますが、校正録や一部の経典を刊行したくらいのようであり、大蔵経全編を刊行した、というわけではなさそうです。また、大蔵経全編を一人で全部写経した色定法師という大物もおられますが、宋版大蔵経一つをみながらだったようですので、3セットも並べるというほどの大事ではなかったかもしれません。

さて、そのようなことをどうやって実現したのか、というところで出てくるのが、増上寺七〇世であった福田行誡上人です。 行誡上人は様々なご活躍をされた人ですが、その偉業のうちの一つに、この大日本校訂大蔵経(縮刷蔵)の刊行事業があります。この事業は天台宗本山派修験道の大先達であり 政府の役職にあった島田蕃根と共に推進したようで、島田蕃根の事績を調べると縮刷蔵の刊行に関する情報が色々出てきますが、 この事業は、廃仏毀釈や増上寺大殿の放火による焼失などの苦難から立ち直るなかで、おそらくはこれまでどこもやったことがないような 大変な事業を、弘教書院を設立するという形で引き受けたようです。『島田蕃根翁』によれば、

最初は弘教書院を京橋山城町に置きましたのを、行誡上人の好意に依つて増上寺大門前の源興院に移し、此處で校合を始めることになりました

とのことで、ここに三大蔵を持ち出して作業しと思われます。自らの宗派のみならず、広く他宗派の僧侶にも集まってもらって編纂を行ったようで、『島田蕃根翁』には 参加者の名前もリストされています。ちょっと長いですが、文字起こししたものを以下に貼り付けておきます。ただ、これでも全員ではないそうです。

  • 東京芝區愛宕下銭照院寄寓眞言宗居士 飯島 道實
  • 美濃國武儀郡高野邑臨済宗永昌寺徒弟 東海 玄虎
  • 美濃國眞島郡垂見邑同宗佛土寺徒弟   桑 宜動
  • 東京築地眞宗應善寺住職  松岡 了厳
  • 近江國蒲生郡豊浦邑天台宗東南寺住職 櫻木谷 慈薫
  • 武藏國橘樹郡神奈川町眞宗長延寺住職 雲居 玄導
  • 駿河國益津郡郡村臨済宗慶全寺前住徒弟 天野 宜格
  • 豊前國宇佐郡日足村曹 洞宗地蔵院前住徒弟 佐藤 道悟
  • 東京下谷區南稲荷町眞宗南松寺住職 櫻木谷 範隆
  • 信濃國南佐久郡跡部村浄土宗西方寺住職 森 亭闇
  • 紀伊國海部郡湊村天台宗明王院住職 蘆津 實全
  • 東京赤坂臨済宗種徳寺住職 朝木 英叟
  • 近江國滋賀郡比叡山坂 本村天台宗正觀院住職 岩本 榮中
  • 伊勢國安濃郡垂見村眞言宗成就寺住職 牧 政純
  • 遠江國榛原郡牧谷原士族 伊佐 岑満
  • 上野國勢多郡大胡町浄土宗養林寺住職 月性 豊民
  • 上總國長柄郡高洲驛日蓮宗實相寺住職 守本 惺亮
  • 駿河國富士郡大鹿村日蓮宗三澤寺住職 齋藤 日一
  • 武蔵國南葛飾郡亀戸村眞言宗普門院住職 千葉 賢永
  • 三河國碧海郡河野村眞宗東派宗園寺衆徒 太田 祐慶
  • 山城國紀伊郡伏見同宗西方寺副住職 兼松 空賢
  • 丹波國與謝郡須津村臨済宗江西寺徒弟 外山 義文
  • 東京芝公園地廣度院住職 千葉 寛鳳
  • 飛騨國大野郡大名田村眞宗入寺住職 平野 素藏
  • 同國益田郡馬瀬村同宗桂林寺住職 日野 厳了
  • 美濃國本巣郡文殊邑曹洞宗成泉寺住職 古田 梵仙
  • 越後國岩船郡平林驛同宗千眼寺住職 山本 法泉
  • 東京築地眞宗敬覺寺住職 大江 凝玄
  • 摂津國有馬郡三田村曹洞宗心月院住職 蘆浦 默應
  • 備中國淺口郡柏島村天台宗福壽院住職 實相 圓隨

この一大事業が比較的短い期間で刊行に至ったということは、それだけ関係者の力が集約されていたということでもあろうかと思います。(このあたりの記述は主にこちらの松永知海先生・梶浦晋先生の記事に拠っています)

ちなみに、なぜ縮刷蔵と言われるのかといえば、懐に入るハンディなものを作ろうとしたためのようです。結果として字が小さくてちょっと読むのが大変なものになってしまった感もありますが、サイズ感を見ていただくと大体以下のような感じです。

f:id:digitalnagasaki:20211116113659j:plain
大正新脩大蔵経と並べたサイズ感

f:id:digitalnagasaki:20211116113538j:plain
縮刷蔵の校異情報(頭注として書かれている)

ちなみに、この編纂にあたっては、上記の『島田蕃根翁』では以下のように記述されています。

校合の順序校合には、種々方法を考へましたが、大躰は、高麗藏に依ることとし、即ち高麗藏を原稿として、其れに 宋藏、元藏、明藏の三藏を對照して、校訂することとし、若し増上寺の元藏が缺けて居れば淺草淺草寺の元藏を對照して之を補ひ、又、増上寺ので缺けた處があれば、忍澂師の校正本を以て之を補ふこととし、略ぼ仕組は出来ましたので、まづ、一人が大きい聲で高麗藏を讀むと、三人の人が其側で、元藏、宋藏、明藏の三つを見て居て、「それ缺けて居る」「此方には斯様ある」と云ふ様に、双方で相違した點を謂ひ立てる、直に筆を執つて、原稿の麗藏の上に、其由を書き付ける、中には、長行と偈頌と違つて居るものもあれば、全く文字の無い所もある、四本相對しても、全く解らぬ時には、□を付けて後日の識者を待つこととし、それでも、何ぞ、外に流布した本に、参照して見るべきものがあつた時には、其を書き加へて、「今按」の二字を加へ、又、全く参照すべきものもなく、而かも意義の通り安いものは、「疑何誤」「恐當作何」と書いて後人の爲に便利を謀りましたか、此時などには、校訂者に博識の人がほしかつたのです、今、當時、校合者の爲た仕事の順序を云へば、 第一業 校本刪補 第二業 句讀 第三業 麗、宋、元、明四本對校 第四業 再校 第五業 印刷對校 此の通りにしていきました

当時は部数限定でそれほど広まらず、結果として、その後しばらくの時を経て、大学院生が師匠に「縮刷版は入手が難しいからなんとかならないか」と訴えるに至り、それも大正新脩大蔵経刊行の動機の一つのなったようですが、それはまた次の話になります。

大正新脩大蔵経刊行に関しては、すでにこちらのブログ記事にかなり書いておりますので、そちらをご参照ください。大正新脩大蔵経は、縮刷蔵の本文や校異情報を引き継いでいる部分もあるようですが、増上寺三大蔵を改めて踏まえたようで、そのようにして刊行された大部の大蔵経が、世界中の研究図書館に所蔵され、標準的に参照される漢文仏典として広く活用されるようになりました。

この件にはさらに続きがありまして、この大正新脩大蔵経が、筆者もお手伝いしているSAT大蔵経データベースや、台湾で推進されているCBETA大蔵経といった、デジタル時代の仏教学研究基盤を支える重要な要素になります。特に重要だったのは、ある文献のある箇所、というのを指定し共有し議論する際に、大正新脩大蔵経における位置情報を利用できることです。世界中の多くの図書館に所蔵されているために現物の確認が容易であり、また、それまでの実績から、研究論文等でも同様に参照されてきたため、デジタル時代の研究基盤として移行していく際に情報共有のための基盤になったのです、学術情報流通の文脈で言えば、すでに識別子がテキスト単位・頁単位・段落・行単位等で付与されている上に、デジタル以前の論文にも識別子が記載されていて、デジタルデータとして入力すればその識別子を用いて機械的に接続できてしまう、のです。いわば、DOI以前のObject Identifierになっているわけです。実はこれは聖書学でも同じような状況で、西洋古典学でもCTS (Canonical Text Services) というのが以前から用いられていますので、古典学では驚くほどのことではなくて、むしろ、日本の古典作品も頑張りましょう、という話なのですが、それはともかくとしまして、このようにしてデジタル研究基盤を支えるプロトコルが大正時代から用意されていて、それを可能にしたのが増上寺三大蔵だった、ということなのです。また、仏典は、仏教研究だけでなく、様々な研究において参照されることがあります。その場合、その内容だけでなく、参照しようと思うような信頼性を有することも重要なのですが、そのような意味でも、増上寺三大蔵に基づいて編纂された、ということがその信頼の基礎に置かれているように思います。

ということで、ちょっと長い割に内容が薄くて恐縮ですが、増上寺三大蔵のありがたみについて、思うところを述べさせていただきました。自分は仏教学を基礎にして仕事をしていますので、日々、このことを忘れることなく取り組んでおりまして、それがユネスコの記憶遺産に申請されることになったのは、たいへんありがたいことであると思っており、ここに至る努力と、これからさらに登録に向けて進めてくださる関係者のみなさまには深く感謝する次第です。

サンスクリット写本 データベースを作った話

最近、サンスクリット写本のデータベースを作りました。といっても、文字起こししたテキストデータベースではなくて、 デジタル画像のデータベースです。世間ではむしろ「デジタルアーカイブ」と言った方が通りがいいでしょうか。

一人で作ったわけではなくて、メタデータを作ってくださった人と、デジタル画像を撮影してくださった企業、 撮影された画像を検品してくださった人、撮影等の費用を捻出するために助成金を取ってくださった人、 その助成金を出してくださった組織、といった色々なステイクホルダーがあり、また、そういったデジタルに 関することとは別に、この資料を集めてくださった人たち、大事に整理・所蔵してきた図書館の方々、という、 現物に関するステイクホルダーの方々もおられます。

私の役割は、そういった方々の間を回って話をしたり色々作っていただいたりしながら、 現物のサンスクリット写本の「デジタル代理物」としての データベース(デジタルアーカイブ)のシステム部分を構築した、ということになります。 ここでは、そのシステム構築の部分の話をちょっと書いておきたいと思います。

まずはじめに

この種のものを構築するときは、仕様書を書いて外注するのが一般的ですが、そもそも元になった資料の性質やとれるメタデータ等々、一通りきちんと 把握していないと良い仕様書は書けません。最近、和本に関してはIIIFの普及もありかなり標準化されてきているので割と簡単にできるようになって いるようにも思えますが、今回はちょっと事情が異なるかもしれないですし、そもそも外注する費用がちょっともったいないということもあり(それより 若手に色々作業してもらう謝金に回した方がいいと思うので)、自分で構築することにしました。

利用するソフトウェア

データの件数としては、公開当初は一部のみを先行公開ということでしたので100件に満たない数で、しかし最終的には数千件のデータを扱うことになります。 数千件ですと、今のコンピューティングであれば単にテキストファイルを用意して検索するだけでもいいのですが、AND検索、OR検索、NOT検索等の 各種検索に加えてソートをしたり等々といった色々な検索機能を拡張していくことを考えますと、そういう機能が充実しているソフトウェアに データを組み込んでしまった方が構築や改良が圧倒的に楽です。そこで、最近この種のことに使っている全文検索ソフトウェアApache Solrを今回も採用する ことにしました。

検索システムへの格納

どういうデータをここに載せて検索させるか、ということについては、相当悩みました。というのは、メタデータはTEI P5の形式で用意されており、そこからいかにしてうまく 必要なデータを取り出して便利に使えるようにするか、ということでした。似たようなこと(=TEIに準拠したメタデータで古典籍の書誌情報検索を提供)をしているサイトとしては、ケンブリッジ大学デジタル図書館がありましたので、あまり凝りすぎずに、ここでできていることを目指せばいいか…というくらいに考えていたところでした。ただ、難しかったこととして、基本的に、写本の一つの束の中には複数の経典が含まれていることが多く、写本の束と含まれている経典のどちらを基準としてデータを構築すべきか、ということについてかなり悩み、ちょうど、Apache SolrでNested Child Document機能が使えるようになったと知ったのでそれもかなり色々試してみたのですが、結局、どうもうまくいかない点がいくつかあって(苦闘の記録)、今回は諦めて、中に含まれている経典の単位、いわゆる子書誌を単位にしたデータ格納をすることにしました。ですので、すべての子書誌が親書誌の情報をそれぞれ有していて、検索後、表示等をする際には親書誌情報単位でまとめて表示する、という関係になっています。

IIIF対応のための作業

画像は、Phase Oneの1億画素のデジタルバックで撮影したものを、JPEG圧縮をかけたPyramid TIFF画像に変換しました。これはTIFF画像の時点では1枚あたり300MBくらいありますが、JPEG圧縮を結構かけているのでPyramid TIFFでのファイルサイズは18Mバイトくらいです。これを、IIP Image Serverを使ってIIIF Image API経由で閲覧できるようにしました。(詳しいやり方はこちら)ついでに、サムネイル画像作成もしたのですが、これはvipsthumbnail というLinuxのシェルで使えるプログラムで繰り返し処理をしたらさくっと終わりました。

その後、IIIF Presentation APIです。IIIF Presentation APIを用意するということは、すなわち、IIIF Manifestファイルを作成するということです。これは、やはりTEI P5に準拠したデータをなるべくよい感じで取り出して、metadeta フィールドに全部押し込んでしまう、というのが基本的な戦略です。TEI準拠で書かれたメタデータファイルと画像のデータやThumbnailなども組み込むPythonのスクリプトを作成して、これはそんなに難しくなくできました。スクリプトのひな形はこちらですが、もう少し整理できたら、今回新たに作成したスクリプトも公開したいと思っております。

Webインターフェイスの作成

さて、検索とIIIF対応画像・IIIF Manifestファイルは作成できました。次はWebインターフェイスです。Webインターフェイスは、凝り出すときりがないので、なるべく簡素に、Bootstrapのテンプレートの中でよさそうなものを使って見ることにしました。「Album」というのがありましたので、それを使ってみることにしたのですが、ただ見せるだけでは面白くないのではないか…と思ってきてしまい、少しだけギミックを作り込んでしまいました。

ちょっとしたギミックの作り込み

f:id:digitalnagasaki:20211002173747p:plain

サムネイル画像の表示は、写本の束の単位で、トップページでも検索結果でもサムネイル画像が表示されるようにした上で、各サムネイル画像に代表的なIIIF Viewerで開くようにリンクアイコンを付けた上で、さらに、「Go」「Back」ボタンでそれぞれの写本のサムネイル画像を遷移できるようにして、さらに、写本の束のタイトルをクリックすると、表示されているサムネイル画像のページでIIIF Viewer (Mirador 3) が開く、という風にしました。また、IIIF Curation Platformでも、該当ページが開くようになっています。Universal Viewerはやりかたがよくわからなかったので諦め、TIFYは、やり方はわかったのですが n枚目という記述を独自形式で行わねばならず、ちょっと面倒だったのでやりませんでした。(でも30分も集中すればできると思いますが)。

というようなことで、このサムネイル画像表示にはちょっと凝ったのですが、なかなか楽しくて気に入っています。

反省点

ただ、反省として、この種のページを作るには、明らかにvue.jsのような仮想DOMを使った方が全体構成としては楽だったはずなのですが、vanilla JSがどうもよくわからないことがあって、しかしjQueryは何も考えずに普通に使えてしまうので、ついjQueryで書いてしまいました…。反省しつつ、vanillaの勉強をしなければと改めて心に誓ったのでした。