PHPでSVGを使って年表を表示

(1/1)
ぱふぅ家のホームページのコーナー「日本史・世界史」では、XMLデータベースにある年表を、SVG (Scalable Vector Graphics)を用いてグラフィカルに表示している。
今回は、この部分のPHPプログラムを紹介する。

2017年(平成29年)10月、世界のどこでイベントが発生したかを俯瞰できるよう、Googleマップ上にマーカーを立てる機能を追加した。

(2025年10月3日)不具合修正
(2025年9月15日).pahooEnv導入

目次

サンプル・プログラムの実行例

PHPでSVGを使って年表を表示

サンプル・プログラム

圧縮ファイルの内容
viewChronologic.phpサンプル・プログラム本体
chronologic.xml年表ファイル
.pahooEnvクラウドサービスを利用するためのアカウント情報などを記入する .env ファイル。
使い方は「各種クラウド連携サービス(WebAPI)の登録方法」を参照。include_path が通ったディレクトリに配置すること。
pahooInputData.phpデータ入力に関わる関数群。
使い方は「数値入力とバリデーション」「文字入力とバリデーション」などを参照。include_path が通ったディレクトリに配置すること。
viewChronologic.php 更新履歴
バージョン 更新日 内容
2.3.1 2025/10/02 不具合修正
2.3.0 2025/09/06 .pahooEnv導入
2.2 2022/04/09 PHP8対応,リファラ・チェック改良
2.1 2017/10/08 年表地図では重複イベントをスキップ
2.0 2017/10/07 年表地図を追加
pahooInputData.php 更新履歴
バージョン 更新日 内容
2.0.1 2025/08/11 getParam() bug-fix
2.0.0 2025/08/11 pahooLoadEnv() 追加
1.9.0 2025/07/26 getParam() 引数に$trim追加
1.8.1 2025/03/15 validRegexPattern() debug
1.8.0 2024/11/12 validRegexPattern() 追加

準備:PHP の https対応

クラウド連携や相手先サイトのデータを読み込むのに https通信を使うため、PHPに OpenSSLモジュールが組み込まれている必要がある。関数  phpinfo  を使って、下図のように表示されればOKだ。
OpenSSL - PHP
そうでない場合は、次の手順に従ってOpenSSLを有効化し、PHPを再起動させる必要がある。

Windowsでは、"php.ini" の下記の行を有効化する。
extension=php_openssl.dll
Linuxでは --with-openssl=/usr オプションを付けて再ビルドする。→OpenSSLインストール手順

これで準備は完了だ。

準備:pahooInputData 関数群

PHPのバージョンや入力データのバリデーションなど、汎用的に使う関数群を収めたファイル "pahooInputData.php" が同梱されているが、include_path が通ったディレクトリに配置してほしい。他のプログラムでも "pahooInputData.php" を利用するが、常に最新のファイルを1つ配置すればよい。

また、各種クラウドサービスに登録したときに取得するアカウント情報、アプリケーションパスワードなどを登録した .pahooEnv ファイルから読み込む関数 pahooLoadEnv を備えている。こちらについては、「各種クラウド連携サービス(WebAPI)の登録方法」をご覧いただきたい。

SVGとは何か

SVG とは "Scalable Vector Graphics" の略で、2Dのベクター画像記述言語として2001年(平成13年)にW3Cが公開した。HTMLへの組み込みも可能である。

公開当初はサポートしているブラウザが少なく、描画にはアドインが必要だったりしたことから普及が進まなかったが、HTML5ブラウザがサポートしたことからブレイク。アニメーション機能も備えていることから、Flashコンテンツの代替もできる。

ベクター描画であることから、画像を拡大縮小してもジャギーがあらわれないことが大きなメリットである。また、テキストの配置座標も細かく指定できるので、ブラウザによって表やグラフのレイアウトが崩れるという心配もない。
今回は利用していないが、レイヤー機能も備えている。

ぱふぅ家のホームページでは、年表のほか、当コーナーで表示しているXMLデータ構造やフローチャートをSVGを使って描画している。

準備:各種定数など

YahooParse.php

  54: // 各種定数(START) ===========================================================
  55: 
  56: // 表示幅(ピクセル)
  57: define('WIDTH', 600);
  58: 
  59: // Yahoo! JAPAN Webサービス アプリケーションID【各自で設定】
  60: // 取得方法:https://www.pahoo.org/e-soul/webtech/php06/php06-01-02.shtm#Yahoo
  61: if (isset($_ENV['PAHOO_YAHOO_APPLICATION_ID'])) {
  62:     define('YAHOO_APPLICATION_ID', $_ENV['PAHOO_YAHOO_APPLICATION_ID']);
  63: else {
  64:     define('YAHOO_APPLICATION_ID', '');
  65: }
  66: 
  67: // リクエストURL【変更不可】
  68: define('YAHOO_MAService_URL', 'https://jlp.yahooapis.jp/MAService/V2/parse');
  69: 
  70: // 解析テキスト(初期値)
  71: define('DEF_SOUR', "Yahoo!JAPANの「日本語形態素解析」は、日本語文を形態素に分割し、品詞、読みがななどの情報を取得できるWebAPIである。\nサーバサイドで利用できる形態素解析は、「PHPとKAKASIを使って単語に分解する」で紹介した「KAKASI」や、「ChaSen」、「MeCab」が有名であるが、サーバに負荷がかかる処理である。この「日本語形態素解析」は処理速度も速く、サーバの負荷分散という意味では有用なWebAPIだ。");
  72: 
  73: // 各種定数(END) ===============================================================

年表データは、下図の構造のXMLファイルとして用意し、そのファイル名は定数 FILE_CHRONOLOGIC に記述する。
この他、年表の幅や、表示フォント・ファミリーなど、各種の初期設定を定数に記述している。

XMLファイルに記述したリンク先ファイルは、定数 URL_PAHOO にあることを想定している。適宜書き換えていただきたい。
また、Googleマップに立てるマーカーは、定数 PATH_MARKER にあることを想定している。こちらも必要に応じて書き換えていただきたい。

Googleマップを利用するために Google Cloud Platform APIキー が必要で、その入手方法は「Google Cloud Platform - WebAPIの登録方法」を参照されたい。

解説:XMLファイル

XMLファイル(xml) chronologic record label イベント・人名 start 開始年 finish 終了年 latitude 緯度(世界測地系) longitude 経度(世界測地系) icon マップ用マーカー・ファイル名 filename リンク先ファイル名 description 記事

解説:配列へ格納

viewChronologic.php

 182: /**
 183:  * 年表:XMLデータを配列へ格納
 184:  * @param   object $xml   年表:XMLデータ
 185:  * @param   array  $items 格納する配列
 186:  * @return  int 格納件数
 187: */
 188: function xml2array($xml, &$items) {
 189:     $cnt = 0;
 190:     foreach ($xml->record as $elm) {
 191:         $items[$cnt]['caption'] = '';
 192:         $items[$cnt]['label']   = '';
 193:         $items[$cnt]['event']   = '';
 194:         $items[$cnt]['caption'] =
 195:             isset($elm->caption? (string)$elm->caption : '';
 196:         $items[$cnt]['description'] =
 197:             isset($elm->description? (string)$elm->description : '';
 198:         if (isset($elm->label))     $ss = (string)$elm->label;
 199:         else if (isset($elm->event))    $ss = (string)$elm->event;
 200:         else if (isset($elm->name)) $ss = (string)$elm->name;
 201:         $items[$cnt]['label']  = isset($ss? $ss : '';
 202:         $items[$cnt]['start']  = isset($elm->start? (string)$elm->start : '';
 203:         $items[$cnt]['finish'] = isset($elm->finish? (string)$elm->finish  : '';
 204:         $items[$cnt]['filename'] = isset($elm->filename? (string)$elm->filename  : '';
 205:         $items[$cnt]['latitude'] = isset($elm->latitude? (double)$elm->latitude : '';
 206:         $items[$cnt]['longitude'] = isset($elm->longitude? (double)$elm->longitude : '';
 207:         $items[$cnt]['icon'] = isset($elm->icon? (string)$elm->icon : '';
 208:         $cnt++;
 209:     }
 210: 
 211:     return $cnt;
 212: }

ユーザー関数 xml2array は、定数 FILE_CHRONOLOGIC で指定した年表ファイル(XML形式)を読み込み、処理しやすいように配列に格納する。

解説:1つのイベントを作成

viewChronologic.php

 380: /**
 381:  * 1つのイベントを作成する
 382:  * @param   array $item 年代記の要素
 383:  * @param   int   $y    Y座標
 384:  * @return  string SVG文字列
 385: */
 386: function mkchronologic_sub($item, $y, $id) {
 387:     global $ChronoPixelYear, $ChronoStart, $ChronoFont;
 388: 
 389:     $font_size = 12;        // 描画テキストのフォント・サイズ
 390:     $start  = __chronoyear($item['start']);
 391:     $finish = __chronoyear($item['finish']);
 392: 
 393:     // $x1:バーの左端X座表  $width:バーの幅
 394:     // $x2:開始年のX座標  $x3:終了年のX座標
 395:     $x1 = (int)(CHRONOLOGIC_LEFT + ($start  - $ChronoStart* $ChronoPixelYear);
 396:     if ($start == $finish) {
 397:         $width = 5;
 398:         $item['start'] = '';
 399:         $x1 -5;
 400:         $x2 = $x1 + 5;
 401:     } else {
 402:         $width = (int)(($finish - $start* $ChronoPixelYear);
 403:         $x2 = $x1 - 5;
 404:     }
 405:     $x3 = $x1 + $width + 5;
 406: 
 407:     // イベントのラベル
 408:     if ($item['label'!'')           $label = $item['label'];
 409:     else if ($item['caption'!'')    $label = $item['caption'];
 410:     else                                $label = '';
 411: 
 412:     // ラベルの配置  $x4:ラベルのX座標
 413:     $len = mb_strlen($label);
 414:     // バーの中
 415:     if ($len * $font_size < $width) {
 416:         $x4 = (int)($x1 + ($width / 2));
 417:         $anchor = 'middle';
 418:     // バーの左
 419:     } else if ($item['start'] == '') {
 420:         $x4 = $x2 - $font_size * mb_strlen($item['start']) - 10;
 421:         $anchor = 'end';
 422:     } else {
 423:         $x4 = $x2 - $font_size * mb_strlen($item['start']);
 424:         $anchor = 'end';
 425:     }
 426:     // バーの右
 427:     if ($x4 - $len * $font_size <0) {
 428:         $x4 = $x3 + $font_size * mb_strlen($item['finish']);
 429:         $anchor = 'start';
 430:     }
 431:     $y1 = $y + 5;
 432:     $height = CHRONOLOGIC_EVENT_HEIGHT - 10;
 433:     $y2 = (int)($y1 + $height / 1.3);
 434: 
 435:     // ラベルとリンク
 436:     if (preg_match('/\.xml/', $item['filename']) > 0) {
 437:         $link = URL_PAHOO . preg_replace('/\.xml/', '.shtm', $item['filename']);
 438:         $color = '#0000FF';
 439:         $tooltip = 'サイト内リンク';
 440:     } else if ($label !'') {
 441:         $link = 'https://www.google.co.jp/search?q=' . urlencode($label);
 442:         $color = '#333333';
 443:         $tooltip = '外部リンク';
 444:     } else {
 445:         $link = '';
 446:         $color = '#000000';
 447:     }
 448:     if ($label !'') {
 449:         if ($link == '') {
 450:             $link =<<< EOT
 451: <text x="{$x4}" y="{$y2}" font-family="{$ChronoFont}" font-size="{$font_size}" fill="{$color}" text-anchor="{$anchor}" >{$label}</text>
 452: 
 453: EOT;
 454:         } else {
 455:             $link =<<< EOT
 456: <a xmlns:xlink="http://www.w3.org/1999/xlink" xlink:href="{$link}">
 457: <text x="{$x4}" y="{$y2}" font-family="{$ChronoFont}" font-size="{$font_size}" fill="{$color}" text-anchor="{$anchor}" onmousemove="ShowTooltip(evt, '{$tooltip}', {$x1}, {$y1}, '')" onmouseout="HideTooltip(evt)" >{$label}</text>
 458: </a>
 459: 
 460: EOT;
 461:         }
 462:     }
 463: 
 464:     // バーとツールチップ
 465:     if ($item['description'!''$tooltip = $item['description'];
 466:     else                                $tooltip = '';
 467:     $str = sprintf('rect%04d', $id);
 468:     if ($tooltip == '') {
 469:         $bar =<<< EOT
 470: <rect id="{$str}" x="{$x1}" y="{$y1}" width="{$width}" height="{$height}" fill="#FFBB00" stroke="none" />
 471: 
 472: EOT;
 473:     } else {
 474:         $bar =<<< EOT
 475: <rect id="{$str}" x="{$x1}" y="{$y1}" width="{$width}" height="{$height}" fill="#FFBB00" stroke="none" onmousemove="ShowTooltip(evt, '{$tooltip}', {$x1}, {$y1}, '{$str}')" onmouseout="HideTooltip(evt)" />
 476: 
 477: EOT;
 478:     }
 479: 
 480:     $svg =<<< EOT
 481: {$bar}
 482: <text x="{$x2}" y="{$y2}" font-family="{$ChronoFont}" font-size="{$font_size}" fill="black" text-anchor="end">{$item['start']}</text>
 483: <text x="{$x3}" y="{$y2}" font-family="{$ChronoFont}" font-size="{$font_size}" fill="black" text-anchor="start">{$item['finish']}</text>
 484: {$link}
 485: 
 486: EOT;
 487: 
 488:     return $svg;
 489: }

ラベル
[Not supported by viewer]
バー
[Not supported by viewer]
開始年/終了年
[Not supported by viewer]
1つのイベントは、上図のように、ラベル、開始年/終了年、バーの3つの要素から構成される。ユーザー関数 mkchronologic_sub を使って描く。

まず、読み込んだデータには、たとえば生年が不明だったり、まだ存命の人物の場合には開始年/終了年が入っていない。そこで、ユーザー関数 __chronoyear を呼び出して、描画用のための仮年号を取得する。
次に、各要素の描画X座標 $x1$x3 やバーの幅 $width や高さ $height を計算する。
バーが短いときは、ラベルはバーの左側に配置。左側に配置して表枠外にはみ出すようだったら、バーの右側に配置するように座標計算する。

次にラベルを、SVGの text要素を使って描く。text-anchor属性を併用することで、バーに対してテキストを揃えている。
また、リンク先がある場合は、a要素を使ってハイパーリンクを張っている。

バーは、SVGの rect要素を使って描く。
description情報がある場合は、ツールチップを表示するようにした。ツールチップはJavaScriptで実装しており、「How to create an SVG “tooltip”-like box?」(Stack Overflow)の回答を参考にした。

最後に開始年と終了年を、SVGの text要素を使って描く。

解説:横軸を作成

viewChronologic.php

 314: /**
 315:  * 横軸を作成する
 316:  * @param   array $items 年代記の要素
 317:  * @param   int   $width 年代記の横幅(単位:ピクセル,200以上)
 318:  * @return  string SVG文字列
 319: */
 320: function mkscale($items, $width, $height) {
 321:     global $ChronoPixelYear, $ChronoStart, $ChronoFont;
 322: 
 323:     // 年の範囲
 324:     $year_min = +99999;
 325:     $year_max = -99999;
 326:     foreach ($items as $item) {
 327:         $year = __chronoyear($item['start']);
 328:         if ($year < $year_min)       $year_min = $year;
 329:         $year = __chronoyear($item['finish']);
 330:         if ($year > $year_max)      $year_max = $year;
 331:     }
 332: 
 333:     // 年の範囲:丸め
 334:     $table = array(
 335:         array(1, 5),
 336:         array(2, 10),
 337:         array(5, 25),
 338:         array(10, 50),
 339:         array(15, 75),
 340:         array(25, 125),
 341:         array(50, 250),
 342:         array(100, 500),
 343:         array(150, 750),
 344:         array(200, 1000),
 345:         array(300, 1500),
 346:         array(400, 2000)
 347:     );
 348:     $period = $year_max - $year_min + 1;
 349:     foreach ($table as $arr) {
 350:         $delta = ($arr[0<25? $arr[0: 25;
 351:         if ($period <$arr[1]) {
 352:             $interval = $arr[0];
 353:             $start    = round($year_min  / $interval* $interval - $delta;
 354:             $finish   = round($year_max  / $interval* $interval + $delta;
 355:             break;
 356:         }
 357:     }
 358:     $ChronoPixelYear = ($width - 80) / ($finish - $start);
 359:     $ChronoStart = $start;
 360: 
 361:     $svg =<<< EOT
 362: <rect x="0" y="0" width="{$width}" height="{$height}" fill="none" stroke="#FFBB00" stroke-width="3" />
 363: <rect x="0" y="0" width="{$width}" height="30" fill="#FFBB00" stroke="none" />
 364: 
 365: EOT;
 366:     for ($year = $start$year <$finish$year +$interval) {
 367:         $x1 = (int)(CHRONOLOGIC_LEFT + ($year - $start* $ChronoPixelYear);
 368:         $x2 = $x1 - 15;
 369:         $y1 = 30;
 370:         $y2 = $height;
 371:         $svg .=<<< EOT
 372: <text x="{$x2}" y="25" font-family="{$ChronoFont}" font-size="14" fill="black" >{$year}</text>
 373: <line x1="{$x1}" y1="{$y1}" x2="{$x1}" y2="{$y2}" stroke="#FFDD88" stroke-width="1" />
 374: 
 375: EOT;
 376:     }
 377:     return $svg;
 378: }

年表の横軸を描画するのがユーザー関数 mkscale である。

まず、年表データの入った配列 $items を総なめして、開始年と終了年を抽出する。
次に横軸の目盛りの間隔を確定する。これは、配列変数 $table に定義している。第一要素が目盛りの間隔で、第二要素が描画期間(開始年~終了年の幅)である。この配列は自由に変更・増減できる。

横軸の背景はSVGの rect要素、目盛り line要素、年号はtext要素を使って、それぞれ描く。

解説:年表を作成

viewChronologic.php

 214: /**
 215:  * 年表を作成する
 216:  * @param   array $info 年代記の要素
 217:  *                  string $items[]['caption']  = 棒グラフ上のキャプション
 218:  *                  string $items[]['label']    = 棒グラフ上のラベル
 219:  *                  int    $items[]['start']    = 開始年(生年)(必須)
 220:  *                  int    $items[]['finish']   = 終了年(没年)(必須)
 221:  *                  string $items[]['filename'] = リンク先ファイル名(XML)
 222:  * @return  string SVG文字列
 223: */
 224: function get_chronologic($items) {
 225:     $width  = CHRONOLOGIC_WIDTH;
 226:     $height = count($items* CHRONOLOGIC_EVENT_HEIGHT + 80;
 227:     $scale = mkscale($items, $width, $height);      // 横軸作成
 228:     $table = mkchronologic($items, $width);     // 年表作成
 229: 
 230:     $svg =<<< EOT
 231: <!-- 年表 -->
 232: <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" onload="init(evt)" width="{$width}" height="{$height}">
 233: <style>
 234: .caption {
 235:     font-size: 14px;
 236:     font-family: Georgia, serif;
 237: }
 238: .tooltip {
 239:     font-size: 12px;
 240: }
 241: .tooltip_bg {
 242:     fill: white;
 243:     stroke: black;
 244:     stroke-width: 1;
 245:     opacity: 0.85;
 246: }
 247: </style>
 248: 
 249: <script type="text/ecmascript">
 250: <![CDATA[
 251: function init(evt) {
 252:     if (window.svgDocument == null) {
 253:         svgDocument = evt.target.ownerDocument;
 254:     }
 255:     tooltip = svgDocument.getElementById('tooltip');
 256:     tooltip_bg = svgDocument.getElementById('tooltip_bg');
 257: }
 258: 
 259: function ShowTooltip(evt, mouseovertext, x, y, id) {
 260:     tooltip.setAttributeNS(null,"x",evt.clientX + 11);
 261:     tooltip.setAttributeNS(null,"y",y + 27);
 262:     tooltip.firstChild.data = mouseovertext;
 263:     tooltip.setAttributeNS(null,"visibility","visible");
 264: 
 265:     length = tooltip.getComputedTextLength();
 266:     tooltip_bg.setAttributeNS(null,"width",length + 8);
 267:     tooltip_bg.setAttributeNS(null,"x",evt.clientX + 8);
 268:     tooltip_bg.setAttributeNS(null,"y",y + 14);
 269:     tooltip_bg.setAttributeNS(null,"visibility","visibile");
 270: }
 271: 
 272: function HideTooltip(evt) {
 273:     tooltip.setAttributeNS(null,"visibility","hidden");
 274:     tooltip_bg.setAttributeNS(null,"visibility","hidden");
 275: }
 276: ]]>
 277: </script>
 278: 
 279: {$scale}
 280: {$table}
 281: 
 282: <rect class="tooltip_bg" id="tooltip_bg"
 283:       x="0" y="0" rx="4" ry="4"
 284:       width="55" height="17" visibility="hidden"/>
 285: <text class="tooltip" id="tooltip"
 286:       x="0" y="0" visibility="hidden">Tooltip</text>
 287: </svg>
 288: 
 289: EOT;
 290: 
 291:     return $svg;
 292: }

年表作成の本体は、get_chronologic である。
ユーザー関数 mkscalemkchronologic を呼び出し年表を描画する。

また、ツールチップを表示するためのJavaScriptも記述してある。

解説:年表地図を作成

viewChronologic.php

 517: /**
 518:  * 年表地図を作成する
 519:  * @param   array $info 年代記の要素
 520:  *                  string $items[]['caption']   = 棒グラフ上のキャプション
 521:  *                  string $items[]['label']     = 棒グラフ上のラベル
 522:  *                  int    $items[]['start']     = 開始年(生年)(必須)
 523:  *                  int    $items[]['finish']    = 終了年(没年)(必須)
 524:  *                  double $items[]['latitude']  = 緯度
 525:  *                  double $items[]['longitude'] = 経度
 526:  *                  string $items[]['icon']      = マーカー・ファイル名(拡張子は除く)
 527:  *                  double $items[]['description'] = 記事
 528:  *                  string $items[]['filename']  = リンク先ファイル名(XML)
 529:  * @return  string HTMLテキスト
 530: */
 531: function get_chronologicalmap($items) {
 532:     global $CountGoogleMaps;
 533: 
 534:     $apikey = GOOGLE_API_KEY;
 535:     $width  = CHRONOLOGICMAP_WIDTH;
 536:     $height = CHRONOLOGICMAP_HEIGHT;
 537:     $mode   = 'ROADMAP';
 538:     $zoom   = 1;
 539:     $lat = '';
 540:     $lng = '';
 541:     $js  = ($CountGoogleMaps > 1? '' :
 542:         "<script type=\"text/javascript\" src=\"https://maps.google.com/maps/api/js?key={$apikey}&amp;region=JP\"></script>";
 543: 
 544:     $flag = FALSE;
 545:     $str = '';
 546:     $arrs = array();
 547:     foreach ($items as $item) {
 548:         if (($item['latitude'!''&& ($item['longitude'!'')) {
 549:             $key = $item['latitude'.',' . $item['longitude'];
 550:             if (! $flag) {
 551:                 // 中心座標
 552:                 $lat = (double)$item['latitude'];
 553:                 $lng = (double)$item['longitude'];
 554:                 $flag = TRUE;
 555:             }
 556:             // ラベルとリンク
 557:             if (preg_match('/\.xml/', $item['filename']) > 0) {
 558:                 $link = URL_PAHOO . preg_replace('/\.xml/', '.shtm', $item['filename']);
 559:                 $color = '#0000FF';
 560:                 $tooltip = 'サイト内リンク';
 561:             } else if ($label !'') {
 562:                 $link = 'https://www.google.co.jp/search?q=' . urlencode($label);
 563:                 $color = '#333333';
 564:                 $tooltip = '外部リンク';
 565:             } else {
 566:                 $link = '';
 567:                 $color = '#000000';
 568:             }
 569:             // 年号
 570:             $year = ($item['start'] == $item['finish']) ? $item['start']:
 571:                 $item['start'. ' - ' . $item['finish'];
 572:             // マーカー
 573:             $marker = (isset($item['icon']) && ($item['icon'!'')) ? PATH_MARKER . $item['icon'.'.png' : PATH_MARKER . 'history.png';
 574:             // 配列へ代入
 575:             $arrs[$key]['latitude']  = $item['latitude'];
 576:             $arrs[$key]['longitude'] = $item['longitude'];
 577:             $arrs[$key]['title']     = $item['label'];
 578:             $label = ($link == ''? "<span style=\"color:blue;\"{$item['label']}" : "<a href=\"{$link}\" target=\"_blank\">{$item['label']}</a>";
 579:             $description = ($item['description'] == ''? '' : "‥‥{$item['description']}";
 580:             $content = "<p style=\"text-align:left;\">{$label}&nbsp;({$year}年)<span style=\"font-size:80%;\">{$description}</span></p>";
 581:             // 重複イベントはスキップ
 582:             if (isset($arrs[$key]['content'])) {
 583:                 if (mb_strstr($arrs[$key]['content'], $content) == FALSE) {
 584:                     $arrs[$key]['content'.$content;
 585:                 }
 586:             } else {
 587:                 $arrs[$key]['content'] = $content;
 588:             }
 589:             $arrs[$key]['marker'] = $marker;
 590:         }
 591:     }
 592: 
 593:     // JavaScript生成
 594:     $str = '';
 595:     $n = 1;
 596:     foreach ($arrs as $arr) {
 597:         $str .<<< EOT
 598: var icon_{$n} = new google.maps.MarkerImage('{$arr['marker']}');
 599: var marker_{$n} = new google.maps.Marker({
 600:     position: new google.maps.LatLng({$arr['latitude']}, {$arr['longitude']}),
 601:     map: map,
 602:     icon: icon_{$n},
 603:     title: '{$arr['title']}',
 604:     zIndex: 250
 605: });
 606: var infowindow_{$n} = new google.maps.InfoWindow({
 607:     content: '{$arr['content']}',
 608:     size: new google.maps.Size(200, 100)
 609: });
 610: google.maps.event.addListener(marker_{$n}, 'click', function() {
 611:     infowindow_{$n}.open(map, marker_{$n});
 612: });
 613: 
 614: EOT;
 615:         $n++;
 616:     }
 617:     // 地図情報がない
 618:     if (($lat == ''|| ($lng == ''))   return '';
 619: 
 620:     $dest =<<< EOT
 621: {$js}
 622: <script type="text/javascript">
 623: <!--
 624: google.maps.event.addDomListener(window, 'load', function() {
 625: var mapdiv = document.getElementById('gmap{$CountGoogleMaps}');
 626: var myOptions = {
 627:     zoom: {$zoom},
 628:     center: new google.maps.LatLng({$lat}, {$lng}),
 629:     mapTypeId: google.maps.MapTypeId.{$mode},
 630:     mapTypeControl: false,
 631:     scaleControl: true
 632: };
 633: var map = new google.maps.Map(mapdiv, myOptions);
 634: {$str}
 635: 
 636: });
 637: -->
 638: </script>
 639: <div style="margin-bottom:20px;">
 640: <div id="gmap{$CountGoogleMaps}" style="width:{$width}px; height:{$height}px;">
 641: </div>
 642: </div>
 643: 
 644: EOT;
 645:     $CountGoogleMaps++;
 646: 
 647:     return $dest;
 648: }

年表地図の本体は、get_chronologicalmap である。
GooleMaps API の使い方は「PHPで地図で指定した場所の天気予報を求める」などで解説しているので、あわせてご覧いただきたい。

冒頭で、Googleマップの縦・横のサイズと、モード $mode、ズーム $zoom を指定している。

次に、引数として与えられた年表情報配列 $items を1つずつ解析していく。
地図の中心は、最初の要素の位置情報を採用する。

参考サイト

(この項おわり)
header