これまで、Wordpressに標準搭載されている「最近の投稿」を少しだけ改造してサイドバーに10件分表示させていたのですが、投稿が蓄積されてくると10件だと少ないと思うようになってきました。かといって、表示件数を20件30件・・・と増やすのもダラダラと長くなってしまって嫌なので、ページ移動するような形で表示させたいなあと思い、新たにプラグイン『Posts List Sidebar Ajax』を作成しました。

Ajax使用なのでリロードすることなくページ移動します。
WordPressにAjax機能を組み込む流れの部分は、以前に作成した「指定したカテゴリーの記事のみを表示するカレンダープラグインCatCalendarAjax」と全く同じなので、実は手抜きなのですが・・・。

プラグイン名:Posts List Sidebar Ajax
特徴:
 ・サイドバーに記事投稿一覧を表示します。
 ・表示する投稿数をカスタマイズ可能です。
 ・「<< 前の記事」「次の記事 >>」のクリックでページ移動出来ます(Ajax使用なのでリロードなし)。
 ・特定のカテゴリーのみをリスト表示することも出来ます。

実際の動作は、右の「投稿記事一覧」のようになります。

ダウンロードや使用方法等はこちら

コメント   

 2013年11月24日(日)
 第33回つくばマラソンを走ってきました。この大会に出場するのは6年ぶり・・・その時に記録した自己ベストを未だに更新出来ていません(もう無理です)。今やすっかり人気大会となり、出走するにもまずはエントリー時のクリック競走を勝ち抜かなくてはならなくなってしまいました。当初は申込み出来なかった時の保険として大田原マラソンを考えていたのですが、今年はその大田原マラソンさえあっという間に締め切られてしまう事態(制限時間4時間なので余裕だと思っていたのに・・・)。もし、つくばのエントリーに失敗していたら、この秋は出る大会がなくなってしまう所でした。

 つくばは学生時代の4年間を過ごした準地元。といっても馴染みのある道路は最初と最後に走る学内ループ道路くらいで、コースの大部分を占める大学の北西方面はほとんど行かなかった(というより何もない田舎なので行く必要がなかった)所なので地の利はありません。6年前とはコースも少し変わっています。東大通りを走る以前のものの方が個人的には馴染みがありましたが・・・。

 参加者は1万人以上でしたが、スタート時の混雑は予想していたよりも少なく、あまりロスもありませんでした。個人的にはスムーズ過ぎて逆に戸惑ったというか・・・「キロ5分くらいのペースで走れればいいかな」と思っていたのですが、考えてみたら春に貧血になって以来、キロ6分くらいか速くても5分半程度でしか走っていなかったので、ぶっつけ本番という状況。そのため、スタート時の混雑を利用して徐々にペースに慣れていこうという作戦だったのですが、いきなりキロ5分で流れてしまったので「このペースで走っては潰れてしまうのではないか?」と心配でした。それでも10kmも走るうちに段々とペースに慣れてきて調子も上がってきました。後はスタミナ切れが懸念されましたが、この日は体調が良かったのか35kmの壁に当たることもなく、10km毎のスプリットタイムを見ると一番速かった10km(20~30km)と一番遅かった10km(0~10km)の差がわずか12秒という、まるでマシンのようなペース配分。
でも、これってペースをセーブしてしまっているが故とも思えるので、好タイムを目指す上では理想とは言えないですね。本当は勇気をもってハイペースで突っ込んでいくべきたと思うのですが・・・まあ、今回は貧血明けにしてはなかなかいいタイムで
走れたので、まずまず満足。

 この日は気温もちょうど良く、風もほとんどないという絶好のコンディション。平坦で元々タイムの出やすいコースなので自己記録を更新された方もきっと大勢いらっしゃることでしょう。

 なお、大会当日の会場までのアクセスについてですが、TX利用の参加者の多くは終点1つ手前の研究学園駅で降りていました。シャトルバスが当駅から出ているためだと思われますが、そのバス待ちの長い行列が出来ていた様子。
 私は終点のつくば駅で降りて徒歩・・・「ペデ」と呼ばれている気持ちのいい歩道を進んで30分弱。到着時間が計算できますし、ウォーミングアップ代わりにもなりますので、こちらの方がお薦めです。

スタート地点

スタート地点
紅葉で色づく学内ループ


ゴール地点

ゴール地点(筑波大学陸上競技場)


ロケット

つくば駅から会場までの徒歩ルート(ペデ)上にはロケットも

コメント   

 前回作成したMAPでは、同一もしくは近くの地点で撮影した写真がある場合マーカーが重なってしまうため、今回は複数のマーカーをまとまる処理を試してみます。

 次のような処理の流れを考えます
 (1)新規マーカーを設置する際に所定の距離以内に別のマーカーがある場合は、そのマーカーと統合して1つにする。
 (2)複数の写真が統合されたマーカークリック時に開く情報ウィンドウの表示内容は、各写真の情報をタブによって切り替えられるようなものとする。

 まずは(1)の処理。対象のマーカーが既存のマーカーと「所定の距離」以内にあるかどうかを確かめるには、その2点間の距離を計算する必要があります。2点間の距離は、それぞれの緯度・経度から求めることが出来ます。以下、関数のソース

  //2点間の距離を計算(GRS80)
  function getDistance(p1, p2) {

     var a = 6378137.0;             //長半径
     var f = 1.0 / 298.257222101;   //扁平率
     var b = a * (1 - f);           //短半径
     var e2 = (Math.pow(a, 2) - Math.pow(b, 2)) / Math.pow(a, 2); //離心率
     var dy = calRadian(p1.lat() - p2.lat()); //緯度差
     var dx = calRadian(p1.lng() - p2.lng()); //経度差
     var my = calRadian((p1.lat() + p2.lat()) / 2.0); //2点の平均緯度
     var w = Math.sqrt(1.0 - e2 * Math.pow(Math.sin(my), 2));
     var m = (a * (1 - e2)) / Math.pow(w, 3);
     var n = a / w;

     return Math.sqrt(Math.pow(dy * m, 2) + Math.pow(dx * n * Math.cos(my), 2));
  }

  //度数→ラジアン変換
  function calRadian(deg){
     return deg / 180.0 * Math.PI;
  }

 緯度・経度から距離を求める計算式はいろいろとあるのですが、ネットで調べた所「ヒュベニの距離計算式」というものが比較的簡単で扱い易そうなので、今回はこれを用いました。球体上の2点の長さ(=弧の長さ)を計算する式をベースにして、地球の扁平率(赤道半径>極半径)を考慮して補正している式のようです。
 上記関数の精度確認のため東京―大阪間の距離を、もっと難しい計算式を使用している国土地理院のサイトでの計算距離と比較してみました。
 <データ>
  東京(都庁) 緯度 35.68952 経度 139.69170
  大阪(府庁) 緯度 34.68630 経度 135.51966
 <結果>
  上記ソースより算出:395950.372m      
  国土地理院にて算出:395911.995m
 誤差は40m弱。東京-大阪という長い距離であることを考えれば、十分な精度と思われます。 
  
 これでマーカー間の距離を取得出来ますので、次は近いもの同士を統合する処理へ・・・。
 当初は、新規マーカー作成時に既存のマーカーとの距離を測り、最も近いマーカーとの距離が所定の距離(例:100m)以内であった場合、そのマーカーと統合した上で位置を中間点に移動させる・・・という処理を考えていたのですが、この方法には多少の問題点があります。

 例えば、A,B,C地点が図-1のような位置関係であるケースについて考えてみます(マーカー統合条件は250m以内とします)。

A,B,C地点の位置関係

図-1 A,B,C地点の位置関係


 データはA→B→Cの順で読み込まれるとすると、
 1.Aのマーカー位置をチェック。近くに他のマーカーはないので統合されず、新規設置となる。
 2.Bのマーカー位置をチェック。最寄りのマーカーAとの距離は244mで250m以内なので、マーカーを統合(新規設置はなし)。マーカー位置はA,Bの中間点へ移動(図-2a)。
 3.Cのマーカー位置をチェック。最寄りのマーカーABとの距離は271mで250mを超えているので、マーカーは統合されず新規に設置される(図-2b)。

AB地点統合後

図-2a A,B地点統合後

A,B統合マーカーとCの位置関係

図-2b A,B統合マーカーとCの位置関係

 これが間違いというわけではないのですが、互いの距離が最も近いB,Cが統合されずに別々のマーカーとなってしまうのはベストとはいえません。やはり、図-3のようにB,Cが統合される方が望ましいです。

B,Cを統合するケース

図-3 B,Cを統合するケース


 そこで、次のようなロジックでマーカー統合処理を行うこととしました。
 1.地点aから所定距離以内に存在する地点のうち、最寄りの地点bを探す。
 2.地点bが見つかった場合、今度は地点bから見た最寄りの地点を探す。
  2-1.これがaだった場合、aとbを統合し位置をaとbの中間点に移動する。 
  2-2.aでなく地点cであった場合はaとbの統合は見送り、地点cから見た最寄りの地点を探す。これがbだった場合はbとcを統合し位置をbとcの中間点に移動する。
 3.以上の処理をループさせ、互いの距離が所定以内となる2地点が存在しなくなるまで繰り返す。

 ソースは以下のようなものとしました。

  //互いの距離が近い地点について、マーカーを統一するための処理
  function uniteLocation(ExifData) {

     var pointA,pointB;
     var uniteFlag;

     for (var i = 0; i < ExifData.length; i++) {
        ExifData[i].Info = new Array();
        ExifData[i].Info[0] = new Array();
        ExifData[i].Info[0] = jQuery.extend(true, [], ExifData[i]);
        for (fld in ExifData[i]) {
           ExifData[i].Info[0][fld] = ExifData[i][fld];
        }
        ExifData[i].United = false;
     }

     do {
        loopFlag = false;
        for (var i = 0; i < ExifData.length; i++) {
           pointB = searchClosestPoint(ExifData,i);
           if (pointB >= 0) {
              pointA = searchClosestPoint(ExifData,pointB);
              if (pointA == i) {
                 ExifData[pointB].United = true;
                 for (var j = 0; j < ExifData[pointB].Info.length; j++) {
                    ExifData[i].Info.push(ExifData[pointB].Info[j]);
                 }
                 var myCenter = getCenterLocation(ExifData[i].Info);
                 ExifData[i].Lat = myCenter.lat();
                 ExifData[i].Lng = myCenter.lng();
                 loopFlag = true;
                 break;
              }
           }
        }
     } while (loopFlag);
  }

  //最寄りの既存マーカーを探す
  function searchClosestPoint(ExifData,myNo) {

     var UniteDistance = 200; // マーカー統合の基準距離(間隔がこの距離よりも長い場合は統合対象外:単位m)
     var myDistance;
     var minDistance = UniteDistance;
     var uniteNo = -1

     for (var i = 0; i < ExifData.length; i++) {
        if (i != myNo && !ExifData[i].United) {
           myDistance = getDistance(new google.maps.LatLng(ExifData[i].Lat,ExifData[i].Lng),
                                  new google.maps.LatLng(ExifData[myNo].Lat,ExifData[myNo].Lng));
           if (myDistance < minDistance) {
              uniteNo = i;
              minDistance = myDistance;
           }
        }
     }
     return uniteNo;
  }

  //複数地点の統合後位置を各地点の重心に設定
  function getCenterLocation(LocationInfo) {
     var sumLat = 0;
     var sumLng = 0;
     for (var i = 0; i < LocationInfo.length; i++) {
        sumLat += LocationInfo[i]["Lat"];
        sumLng += LocationInfo[i]["Lng"];
     }
     return new google.maps.LatLng((sumLat / LocationInfo.length),(sumLng / LocationInfo.length));
  }

 関数「uniteLocation」の引数「ExifData」は取得したディレクトリ内の画像ファイル情報(配列)です。
 7行目~15行目は統合前の準備。各地点データについて「Info」プロパティを新規作成し、そこに地点情報を全て収納しています。この「Info」には、統合時に対象地点の情報も追加するようにします。14行目の「United」プロパティは、その地点が統合されたか否かを表すフラグで、初期値にfalse(=未統合)をセットしています。
 そして17行目~36行目にて統合のループ処理。20行目で、
   pointB = searchClosestPoint(ExifData,i);
 としています。「searchClosestPoint」は対象(引数iに該当)の最寄り地点を探す関数です(39行目~58行目)。地点iの最寄りの地点(pointB)が見つかった場合、
   pointA = searchClosestPoint(ExifData,pointB);
 と、今度はpointBから見た最寄りの地点(pointA)を探します(22行目)。そしてpointAと地点が同一であった場合はpointA(=地点i)とpointBを統合します。統合元(pointB)のUnitedをtrueにし(24行目)、統合先(pointA)のInfoに統合元(pointB)の情報を追加します(25行目~27行目)。
 28行目で呼び出している「getCenterLocation」はInfo内に収納されている全地点の重心(へそ)を取得する関数で(60行目~69行目)、統合先(pointA)の緯度・経度を、ここで求めた重心地点に更新します(29,30行目)。31行目でloopFlag=trueとしているのはループを続けるための処理で、所定の距離より近い2点が見つからなくなった時点(=for文を抜けた時点でloopFlag=falseのまま)でループを終了します。

 以上で、複数地点の統合処理部分は出来ましたので、最後にこれをマーカー表示させ、クリック時に複数地点に対応させた情報ウインドウを開くようにします。

  //マーカー設置
  function setPhotoMarker(myExifInfo) {

     var myTitle = "";
     var mNo = PhotoMarker.length;
     var myIconImg;

     for (var i = 0; i < myExifInfo.Info.length; i++) {
        if (i > 0) {
           myTitle += ",";
        }
        myTitle += myExifInfo.Info[i]["FileName"];
     }

     if (myExifInfo.Info.length > 1) {
        myIconImg = "createmarker.php?image="+ markerImage + "&string=" + myExifInfo.Info.length
                     + "&color=000000&fontsize=12&offsety=-4";
     } else {
        myIconImg = markerImage;
     }
     PhotoMarker[mNo] = new google.maps.Marker({
        position: new google.maps.LatLng(myExifInfo.Lat,myExifInfo.Lng),
        map: map,
        title: myTitle,
        visible: true,
        draggable: false,
        icon: myIconImg
     });
     PhotoMarker[mNo].Info = myExifInfo.Info;

     google.maps.event.addListener(PhotoMarker[mNo], 'click', function() {
        setInfoWindow(map,mNo);
     });
  }

 マーカーの設置に関しては概ね前回と同じですが、複数地点を統一しているマーカーに関しては16行目で
   myIconImg = “createmarker.php?image=”+ markerImage + “&string=” + myExifInfo.Info.length
            + “&color=000000&fontsize=12&offsety=-4”;

 と、動的に生成したテキスト付加のマーカー画像を表示させています。createmarker.phpの内容については、以前の投稿「テキスト付きのマーカーの表示」を御参照下さい。

 情報ウインドウの内容は、タブによって表示内容を切り替えたいのですが、htmlには「タブ」要素は存在しないので、Javascriptを使用して擬似タブを表示させることにしました(図-4)。

情報ウインドウの表示内容サンプル

図-4 情報ウインドウの表示内容サンプル(複数地点の情報をタブで切替え)

 
 各地点毎に緯度・経度、方角などの情報は異なりますし、またタブページ数を地点数に合わせる必要がありますので、smarty的感覚でテンプレートを用意して各地点の情報に応じて動的にhtmlを生成する方法にしました。

<情報ウインドウのHTMLを動的に生成>
  テンプレートファイル(infowindowtemplate.js)

  //情報ウィンドウの設定
  function setInfoWindow(myMap, markerNo) {

     if (currentInfoWindow) {
        currentInfoWindow.close();
     }
     var myContent = "";
     var myTabPage = "";
     var myTabPages = "";
     var myTabBtn = "";
     var myTabBtnStyle, myTabBtnClass;
     var myTabBtns = "";
     var myTabBtnDisplay, myTabBtnScrDisplay;
     var pageNo;

     for (var i = 0; i < PhotoMarker[markerNo].Info.length; i++) {
        pageNo = i + 1;
        myTabPage = TabPage;
        for (fld in PhotoMarker[markerNo].Info[i]) {
           myTabPage = myTabPage.replace(RegExp("#"+fld+"#","g"),PhotoMarker[markerNo].Info[i][fld]);
        }
        myTabPage = myTabPage.replace("#pageNo#",pageNo);
        if (pageNo == 1) {
           myTabPage = myTabPage.replace("#display#","block");
        } else {
           myTabPage = myTabPage.replace("#display#","none");
        }
        myTabPage = myTabPage.replace(/#Image#/g,imageDir+PhotoMarker[markerNo].Info[i]["FileName"]);
        myTabPages += myTabPage;

        myTabBtn = TabButton;
        myTabBtn = myTabBtn.replace(/#pageNo#/g,pageNo);
        myTabBtnClass = "tabOff";
        if (pageNo <= 5) {
           myTabBtnStyle = "display:block;"
           if (pageNo == 1) {
              myTabBtnClass = "tabOn";
           }
        } else {
           myTabBtnStyle = "display:none;"
        }
        if (pageNo % 5 == 1) {
           myTabBtnStyle += "border-left-width:1px;"
        }
        myTabBtn = myTabBtn.replace("#style#",myTabBtnStyle);
        myTabBtn = myTabBtn.replace("#class#",myTabBtnClass);
        myTabBtns += myTabBtn;
     }
     myTabBtnScrDisplay = "none";
     if (PhotoMarker[markerNo].Info.length > 1) {
        myTabBtnDisplay = "block";
        if (PhotoMarker[markerNo].Info.length > 5) {
           myTabBtnScrDisplay = "inline";
        }
     } else {
        myTabBtnDisplay = "none";
     }
     myContent = InfoWindowContents;
     myContent = myContent.replace("#TabPage#",myTabPages);
     myContent = myContent.replace("#TabButton#",myTabBtns);
     myContent = myContent.replace("#btnDisplay#",myTabBtnDisplay);
     myContent = myContent.replace("#btnScrDisplay#",myTabBtnScrDisplay);
     var infowindow = new google.maps.InfoWindow(
        {  
           content: myContent
        }
     );
     infowindow.open(myMap, PhotoMarker[markerNo]);
     currentInfoWindow = infowindow;
  }

 以上で一応出来上がり。下図のようなMAPが表示されます。

MAP出力例

図-5 MAP出力例(複数画像をまとめてマーカー表示)

 

ソース一式ダウンロード(ZIP圧縮)
※PHP5.2未満のバージョンではJSONライブラリが標準搭載されていないため、追加で入手・設置する必要があります。 
※当圧縮ファイルには以下のオープンソースを同梱しています。これらのオープンソースの使用条件は、それぞれの適用ライセンスに従います。
・jQuery(jquery-1.10.2.js)
  MIT、GPLライセンス ※著作権など詳細はjQueryのサイトにてご確認下さい(当ファイルの冒頭にも記載されています)。
・Alte Haas Grotesk(font/alte_haas_grotesk内のファイル全て) Yann Le Coroller作のフォント。
  ライセンスフリー(ドキュメントAlte Haas Grotesk licence.rtfにてご確認下さい)
※上記同梱物以外の著作権は作者である田村嘉朗が保有しています。フリーウェアですので自由に使用・改変していただいて結構です。※当ソースの使用または使用不能により生じたいかなる障害・損害について、作者は一切の責任を負いません。各自の責任においてご使用ください。
※非営利目的での転載・再配布は自由です。報告なども特に必要ありません。ただし、転載・再配布の際、許可なくアーカイブの内容を改変することは禁止します。

コメント   

 11月9日(土)
 「知られざるミュシャ展 -故国モラヴィアと栄光のパリ-」を観覧に横浜のそごう美術館へ。

そごう美術館 ミュシャ展

そごう美術館
ミュシャ展


 ここの美術館は、土日でもいつも空いている印象でしたが、この日は結構混んでました。ミュシャ人気なのでしょうか?
 ミュシャ展は2、3年前に三鷹の美術館で観覧しており、その後も作品を見る機会が何度かありましたが、ミュシャ好きとしては外せません(といいつつ、春に六本木で開催されたミュシャ展は見逃してしまったのですが)。
 今回の展示作品のうち30点以上が日本初公開とのことですが、どれが初公開作品なのかは良くわかりませんでした。何度も見ている「ジスモンダ」が実は一番良かったり(ミュシャといったらサラ・ベルナールでしょう)・・・。
 一番好きな「黄道十二宮(Zodiac)」は残念ながら今回出品されておらず、展覧会コピーの「知られざる」という程の作品もありませんでしたが、それでも十分満足出来る内容でした。ミュシャは本当にセンス抜群ですね。画家としてはともかく、デザイナーとしてのミュシャを超える人材はこの100年間生まれていない・・・というのは大袈裟?

 ネットでダウンロード出来る割引券を印刷して持参すると200円引きになります(大人1,000円→800円)。
 ・リンク:https://www2.sogo-gogo.com/wsc/511/N000016587/0/info_d

 会期は12月1日(日)まで。

「スラブ叙事詩」展

「スラブ叙事詩」展


花、ホームデコ

花、ホームデコ

コメント   

 11月3日(日・祝)
 行きたい行きたいと思っているうちに、会期末間際となってしまった展覧会「仙厓と禅の世界」。日比谷の出光美術館まで慌てて観覧に行ってきました。
 白隠と並ぶ禅宗画家の仙厓義梵。白隠も好きですが、仙厓の画は更にくだけた感じ。「ほっこり」という言葉がぴったりくるような癒し系の絵がほとんどです。
 解説によれば、各画には教訓めいた深い意味が込められているとのことですが、あまり難しいことは考えずに楽しく観覧しました。

 この後は「モローとルオー展」を観覧(2回目)。禅画とキリスト教画の2本立て・・・いかにも日本人的?

自画像画賛

自画像画賛


指月布袋画賛

指月布袋画賛


坐禅蛙画賛

坐禅蛙画賛


狗子画賛

狗子画賛

コメント