【頭の整理】日本での「テキストデータベース」作りの2つ目のステップあたり

前回記事では、「テキストデータベース」作りに関して、その意義とか、テキストデータそのものをどうやって得るか、というようなことをメモしてみた。

今回は、とりあえずテキストデータが入手できたあと、どうすべきか、ということをメモしておきたい。ややもったいをつけて書いているところもあるかもしれないが メモなので気にしないでいいただきたい。

テキストデータ。これが実は非常に難しい。まず、ボーンデジタルなものには生じないが、デジタル以外の媒体に基づくテキストデータにはしばしば大きな問題がある。 それは、「元の媒体上でのテキストと完全に同じではない」ということだ。

すごく細かい話をすれば、字形や文字の大きさは必ず少し異なるはずだ。 このあたりは、まだ内容の相違ということにはならないが、しかし、読み手側が受ける印象には 少し違いが出てくるだろう。たとえば、目が悪い人向けに少し大きな文字にしているかどうか、 あるいは、ディスレクシア向けにユニバーサルデザイン(UD)フォントを使っているかどうか、 ということも、やはり読み手を意識した時にはやや大きな違いとなるだろう。テキストの内容を 分析する場合でも、テキストに対して読み手がどう考えたかということを研究対象とするなら そのあたりも関わってくることがあるかもしれない。もう少し大雑把な話をすれば、石に彫られたものと 紙に印刷されたものとでは受ける印象は大きく異なるだろうが、プレーンなテキストデータには そういった点も特に反映されることはない。

ここから少しスコープを広げると、字体の違いが出てくる。旧字体と新字体を丸めてしまうことは 現在も割とよく行われるようだが、その中には、著者は異なる文字として使い分けた2つの 字体を丸めてしまうとういこともあるかもしれない。できることなら、その使い分けに どういう意味があるかを判断するのは、それを読み分析する側でありたい。しかしながら、 たとえば外注で企業に文字起しを依頼する場合などは、あらかじめJISの第○水準の文字で、 という風に仕様書で限定をかけることになるので、どうしても丸めざるを得ないことになる。

ここまでは漢字の話だが、仮名も丸めたり色々なことをする場合がある。仮名については、 たとえば源氏物語の研究でよく用いられてきたものの一つである『校異源氏物語』では 字母の違う仮名を現代のひらがなに丸めてしまっている。一方で、近年は字母の違いを対象にした 源氏物語研究も行われており、この点は今後大きな課題の一つになっていくかもしれないと 個人的には思っている。

このあたりの、ルールベースでの問題とは別に、単なる誤転記という問題もある。 これは、最終的には人力でチェックするしかないので、正確性を期すなら非常に難しい。 むしろ、「少し間違っていても利用可能なもの」として流通させ利用する方が よいのかもしれない。そもそも、紙媒体でも誤植が混入することはしばしば生じるので あり、デジタルだけの問題でもないということでもあるだろう。

というようなことを踏まえていくと、元資料がデジタル媒体でないテキストデータの 扱いはなかなか一筋縄ではいかないようである。とはいえ、こういった状況に向けた 解決策もちょこちょこあるのでそれは次回にまた書いてみたい。

【頭の整理】日本での「テキストデータベース」作りの最初のあたり

標題の件につき、少し頭を整理するためにメモを残しておく。多分これが本来的なブログの使い方なのではないかと思うので、情報収集したい人にはあまり有益ではないかもしれず申し訳ないがご容赦いただきたい。

テキストデータベースを作る、という取組みは、テキスト研究をしているとどうしても関心を持たざるを得ない。もちろん、 テキストとして書かれたものだけを対象としたところで人間文化の何が明らかにできるのだろうか、という立場もあるとは 思うのだが、テキストほどに高度に集約的で持続性も高い情報伝達手段はなかなかないので、一定の有用性は認めてよいのでは ないかと思っている。

一方で、テキストは、Unicodeなどの文字コードに準拠して並べていけば割と高度な処理が比較的容易に可能となるので、 テキストデータベースをどういう風に作っていくかということは結構重要なのである。 もちろん、Unicodeなどが出てくる以前から、色々なローカルな文字コードを駆使してテキストデータベースは 作られてきていて、日本でも1980年代にはすでにテキスト・データベース研究会というのが活発に活動していたそうだ。

最近は、テキストデータと言えば、みんなで書いているMSワードや一太郎、LibreOffice、Google Docs等の文書、エクセルやパワポ、 Google やLibreOfficeの同種のソフトで作られたデータの テキスト部分、SNSへの毎日の大量の書き込みやブログ等へのほどほどの書き込みなど、いわゆるボーンデジタルのテキスト データが毎日せっせと大量に作成されていて、それらをざっくり分析できれば大変有用な分析が様々に行えそうである。 ePub等で販売されている電子書籍のデータもこれに入るだろうし、テレビの字幕データとか、他にも色々有用そうなものがある。 これについては、技術的には比較的容易だがデータ・文書の権利関係をクリアにするところが基本的には難しい。

一方、ボーンデジタルでないテキスト、には、著作権が切れているものと切れていないもの、著作権がどうなっているか わからないもの、の3種類がある。

著作権が切れているものは、基本的に自由に使える。が、著作権が切れているかどうかの確認は、権利者の没年情報が 必要になるため、著名人はともかくそうでない人は結構確認が難しい。著名人でも、没年の確認は難しい、という 場合もある。

ではこれを踏まえてどうするか、ということだが、基本的に、著作権が切れているものは自由に使えるので それをテキストデータ活用のベンチマークとして扱うのが一つのわかりやすい道だろう。これを通じて明らかに できた様々な活用方法を、権利関係的に自由に活用できることが稀なボーンデジタルテキストの分析に活用することで、 より深い社会文化研究にもつなげられるのではないか、というのが期待されることの一つである。

とはいえ、著作権が切れているものには、現代的な日本語で書かれているものはあまり多くない。しかも、 少し時代を遡ると古文&くずし字になってしまうため、テキストデータベース作りにおける難易度は高くならざるを 得ない上に、そのテキストの分析手法をそのまま現代に適用するのはちょっと難しい。もう1つ2つ ステップが必要になる。

したがって、比較的新しいところにターゲットをあてるのがよいのかもしれない。明治中期~昭和初期あたり だろうか。このあたりは、最近、国立国会図書館で次世代デジタルライブラリーを公開するなかで OCRによるテキストデータ化もかなり進んでいることが明らかになり、これを用いることで、もう一歩進んだ 取組みが可能となるだろう。

古い日本語の分析手法は、すでに国立国語研究所により古い日本語のUnidicが時代ごとに公開されていて 形態素解析がある程度は可能となっており、また、 情報処理学会人文科学とコンピュータ研究会等では江戸時代以前のテキストデータを 対象とした固有表現抽出やトピックモデリングなどに関する発表が着々と行われており、テキストデータの 分析手法も、まったくできないわけではない。ただ、これはまだ研究段階であり、しかし研究段階だから 面白いということでもある。

また、古い日本語テキストの場合、OCRもかかりにくいということがあり、テキストデータベース 作りの難関の一つだったが、最近は、くずし字OCRやクラウドソーシング翻刻など、自動文字読み取り という方向や人力作業の輪を広げていく方向など、現在の技術水準で可能なことが徐々にこの 領域にもおりてきている(というより、おろしてくださっている若手研究者の方々に感謝である)。

というようなことで、古い方に関しては、精度の問題はあるにせよ、少しずつ進んできている。 その種の仕事とボーンデジタル日本語テキストの分析の間には無数の網の目のような連携の 可能性があるが、そのあたりはまた次に。

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のデータを地図上にマッピングして簡単に閲覧できるところまできました。とりあえず今日はこの辺にしておきたいと思います。