WordPressの記事に埋め込むLeafletの地図に複数のピンを挿すためのショートコードを作ってみた。

By | 2020年9月9日 , Last update: 2022年8月7日

はじめに

前の記事にR246及び高樹町通り(骨董通り)の交差点(南青山五丁目交差点)並びに六本木ヒルズにピンを挿したLeafletの地図を埋め込もうと思ったのですが、昔やっつけで作ったショートコードが地図上の1ヵ所にしかピンが挿せない仕様になっていたことを今更ながら思い出しました。

そこで、この際そのショートコードを書き直して、複数のピンを挿すことができるようにしてみました。

…だが、しかし。

データベースの助けが必要だったり、その他いろいろと手直しが必要なことがわかりましたので、そのあたりについても書きます。

スポンサーリンク

最初に考えたこと。

タグの名前を考えます。

まず、作成するショートコードで使用するタグの名前を考えます。

ピンでLeafといえば針葉樹ということで、pineleafに決定です。

ショートコードの引数で複数のピンについての情報を直接指定してみる→ボツ。

次に、ショートコードのタグの引数に複数のピンについての情報を何とか埋め込むことができないか考えます。

↓のように書くことができればOKなのですが…

[pineleaf pins=”[ピン1の情報],[ピン2の情報],…”]

 

「ピンの情報」に緯度・経度及びポップアップに表示させたい文字列を設定できれば、あとはショートコード側の実装を頑張って、Leafletのmarkerやpopupにそれらの情報を渡すことにより一見うまくいきそうにも思えます。

しかーし。

この「ポップアップに表示させたい文字列」というのが曲者で、HTMLのタグを入れることができます。したがって、「ポップアップに表示させたい文字列」の一部としてimgタグやaタグのように属性の指定が一般的に必要なものも指定できる…はずなのですが、どことなく動作が不安定で、imgタグやaタグが処理されないことがあるように見えます(この記事を最初に書いた時点(2020年9月)の情報です)。

また、仮に動作を安定させるコツがあったとしても、ピンの情報としてタグの属性値を指定する際にはダブルクォーテーションが必要になり、かつそれらはダブルクォーテーションで囲まれた文字列の一部として指定されるため、ことごとくエスケープする必要があります。エスケープ入りの文字列は可読性がいまいち良くない上に何かのはずみでエスケープを忘れるとエラー箇所の確認と修正に手間取ることが予想されます。


スポンサーリンク

そこで、ショートコードのタグの引数でポップアップに表示させたい文字列を緯度及び経度との三つ組みとして一組以上指定するアプローチはひとまず不採用とし、別のアプローチを考えることにしました。

そうだ、データベースを使おう。

ここまでの考察で、ショートコードのタグの引数でポップアップに表示させたい文字列を緯度及び経度との三つ組み(以下、単に「三つ組み」と書きます。)を記事中に埋め込むことが難しそうであることがわかったので、データベースにテーブルを作って三つ組みのデータをIDを割り当てつつ保存することを考えます。

まず、データベースのテーブル作成用のSQL文を書きます。

以下のような感じになります。

-- See https://pandanote.info/?p=6619 for details.
drop table if exists leaflet_markers;
create table leaflet_markers (
id int(11) not null auto_increment,
lat float not null,
lon float not null,
popup varchar(4096) not null,
primary key(id)
);

上記のSQL文を用いてデータベース上にテーブルを作成し、このテーブルに三つ組みのデータを作成してinsertするとIDが割り当てられますので、それらをショートコードのタグの引数としてカンマ区切りで指定します。

すると…


スポンサーリンク

のように、ショートコードのタグ側では簡単な引数の設定を行うだけで、ポップアップを複数表示させることができます。

ショートコードのコード例

コード例

前節で定義したデータベースからショートコードのタグ側の指定に基づいて三つ組みを読み出して、それを地図上に表示するためのショートコードは以下のような感じになります。

<?php
/*
See https://pandanote.info/?p=6619 for details.
*/
function pineleaf($atts) {
global $wpdb;
$atts = shortcode_atts(array(
"lat" => 35.45585,
"lon" => 139.64204,
"zoom" => 16,
"markeridlist" => "",
"popup" => "横浜ハンマーヘッド<br><img src=\"https://pandanote.info/wordpress/wp-content/uploads/2019/11/P_20191117_105315_vHDR_On_HP_a.jpg\"/>"
),$atts);
$str = "";
atts).strval(mt_rand()));
\"_pineleaf_map_number."\" style=\"height:250px\"></div>
<script type=\"text/javascript\">
onload_event_".$_pineleaf_map_number." = function(){
var map = L.map('mapid".atts["lat"].", ".atts["zoom"].");
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '&copy; <a href=\"https://osm.org/copyright\">OpenStreetMap</a> contributors'
}).addTo(map);";
atts["markeridlist"]),function($v) {
return preg_match("/\d+/",Misplaced &v > 0;
});
if (count($markerlist) == 0) {
atts["lat"].", ".atts["popup"]."').openPopup();";
} else {
markerlist).")";
wpdb->get_results($query,ARRAY_A);
foreach(row) {
row["lat"].", ".row["popup"]."')).openPopup();";
}
}
$str .= "};
addOnloadEvent(onload_event_".$_pineleaf_map_number.");
</script>";
return $str;
}
function enable_pineleaf() {
?>
<script type="text/javascript">
function addOnloadEvent(f) {
if ( typeof window.addEventListener != "undefined" )
window.addEventListener( "load", f, false );
else if ( typeof window.attachEvent != "undefined" ) {
window.attachEvent( "onload", f );
} else {
window.onload = f;
}
}
</script>
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css"
integrity="sha512-xodZBNTC5n17Xt2atTPuE1HxjVMSvLVW9ocqUKLsCC5CXdbqCmblAshOMAS6/keqq/sMZMZ19scR4PsZChSR7A=="
crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.7.1/dist/leaflet.js"
integrity="sha512-XQoYMqMTK8LvdxXYG3nZ448hOEQiglfqkJs1NOQV44cWnUrBc8PkAOcXy20w0vlaXaVUearIOBhiXZ5V3ynxwA=="
crossorigin=""></script>
<?php
}
add_action('wp_head','enable_pineleaf');
add_shortcode('pineleaf','pineleaf');
?>

工夫した点は以下の通りです。

  1. 以前Leafletを扱うコードを書いた際には地図を表示するためのdivタグに割り当てるidが重複しないようにするために、PHPのグローバル変数を定義して表示させる地図の数をカウントし、その値を前述のdivタグのidの一部としていました(“mapid1″,”mapid2”,…)が、各地図に対してSHA-512のハッシュ値を割り当てるよう変更しました(16行目)。
  2. Leaflet本家のpopupの表示例を使って表示させたいピン及びポップアップの数だけ
    L.marker([”.$atts[”lat”].”, “.$atts[”lon”].”]).addTo(map).bindPopup(‘”.$atts[”popup”].”‘).openPopup();

     
    的なコードを書いてしまうと、同時に表示できるポップアップが最大で1個になってしまうので、ポップアップのautoClose設定をfalseに設定しました(この設定はポップアップごとに行います)。

    上記の例ではbindPopup関数の引数にポップアップに表示させたい文字列を指定していますが、ここにはポップアップのオブジェクト自体を引数として設定できます。そこで、ポップアップのオブジェクトをまず作成し、そのオブジェクトに対してautoClose設定をfalseに指定して、かつ表示させたい文字列も設定した上でbindPopup関数の引数として設定する実装としています(34行目)。

ついでに気がついたこと。

以前Leafletを扱うコードを書いた際にはやっつけでコードを書いたこともあり(※個人の言い訳です。)、DOMを直接操作する方法によりLeafletを有効化するためのタグを動的に埋め込んでいました。

しかし、今後は本Webサイトでも複数のページにおいてLeafletを使った地図を埋め込む可能性が出てきたので、wp_headにフックを追加する方法で、Leafletを有効化するためのタグをheadタグの子要素として挿入することにしました(前節のコード例の44-64行目)。

まとめ

wp_headにフックを追加する方法でLeafletを有効化するためのタグ(linkタグ及びscriptタグ)を読み込ませる方法に変更したので、泥臭さは大幅に改善されたと思います(※個人の感想です)。

データの格納にデータベースを使うことにしたことと、データベース上のテーブルにHTMLのsnippetを書き込むごとにSQL文を実行するのはいろいろと面倒なので、これはプラグイン化不可避かなぁ…

と思い始めたのですが、ちょっと敷居が高そうなので、また後日にします。

panda先生の次回作にご期待いただければと思います。🐼

この記事は以上です。

References / 参考文献