概要
この記事では、選択した周囲の神社仏閣から自分だけのパワースポットを探し出すアプリ「結界発見」の制作の経緯や五芒星・六芒星探索アルゴリズムの説明などを書いている。
まだ試したことのない人はぜひリンクから遊んでみよう。
きっかけはこのツイートからはじまった
陰謀論ウォッチャーとして有名なライター雨宮純氏のツイートをふと見つけたことから始まる。
私はもとよりオカルトや不思議な噂に興味がある方である。某ルポ漫画で「某公園に存在しないはずの大仏を見た人がおり、その人は後日出世した!」という話を読んだ際には、写真などの情報から場所を特定して怪談ハンターとして赴く程度にはこういう話が好きだ。
といいつつもさすがに神社仏閣を結んだ五芒星・六芒星話については「日本にはたくさん神社仏閣があるから、あちこちで結べる気がするなあ」と思っていた。実際、n個の点から5点選ぶ時の組み合わせ数はnの値が大きくなると爆発的に増えていくのでこの直感は正しいだろう。
具体的な式を図に残すが、神社仏閣の数nを増やしていくと爆発的に組み合わせ数が増えていく。n=10の時は252通りしかないが、n=100で七千万、n=1000で八兆通りもある。組み合わせ爆発というやつだ。
本当はこの適当に選んだ点が正五角形である確率を求めなければ検証はできないのだが、ここまで組み合わせが増えるのならばさすがにあちこちに五芒星が結べるだろうなと仮説を立てていた。
この「神社仏閣の点が多ければ五角形はどこでも結べる」仮説は正しいのか、試しに可視化したら面白いんじゃないかとついメモしたくなり、引用リツイートした。普段ならばこういう怪しいネタにはあんまり関わらないようにいいねも押さない人間なのだが、今回はなぜか書いてしまった。陰謀かもしれない。出社前の朝のことである。
上記のツイートをしたまま出社、仕事を終えてポストを見ると想像以上にリツイートやいいねを押されていてビックリした。
先月公開したブラウザゲーム「将棋RPG」よりもリツイートされてるじゃん!とビックリした私は、早速この件の開発を決意した。生きがいにしている毎月のゲーム制作よりも注目が集まっているのならばこれは作るしか無かろう。
自分のスキと理想で作り上げるプロダクトアウト指向でモノづくりしている時に「自分の創作物が世間に求められること」というのはかなり稀なことである。大抵の場合は自分と世間の求めるもののギャップを感じてしまうものだ。生きるためのビジネスならば市場のニーズにいくらでも合わせられるが余暇の時間でそこまでやる程人生は長くない。
そんな中、「世間の需要と作りたい方向性が合致するチャンスの糸」みたいなものが時折垂れてくるのだ。こんな求められたのだから作るしかあるまい。おそらく技術的課題はなさそうだから、地図情報を提供してくれるサービスを見つけたら一日でできるでしょう!と決めて翌日会社に有休申請を出したのであった。
地図情報サービスの選定
このあたりから、新しくモノづくりしたい人向けに専門的な情報を平易にして書いていく。何書いてるのかわからん!という方は雰囲気で読んでね。
このWebアプリにおいて一番大変だろうと思った課題がこの地図情報サービスの選定である。ライセンスや利用規約をよく見て今回の用途で使用しても問題なさそうなサービスを選ぶ必要がある。
この手のサービスはプログラムからサーバーに「こんなデータちょうだい!」とリクエストを投げて返信データを受け取る。こういった受け取り口のことをAPI(Application Programming Interface)と呼ぶ。APIってよく聞くけどなんやねんと思っている方もいたかと思うのだが、インターフェースは入出力部分のことなので、「これはプログラムの入出力のことだな」と覚えると忘れないはずだ。
色々検討した結果、Leafletという地図表示ライブラリとOpenStreetMapというオープンソースの地理データベースサービスを利用することにした。
この手のAPIは制作者もサーバーをたてて通信部分をサーバーサイド言語で作成する必要があるのだが、OpenStreetMapはフロントエンドからリクエストを投げても問題なくレスポンスが来る。つまりJavaScriptだけ書けば完結する。
つまり楽ということである。
ライセンスも見たがクレジット表記さえ適切に行えば問題なく使えそうである。なんなら、X上で丁寧に質問を回答していただいたので、この場を持って御礼と共にこのサービスを布教させていただく。興味ある方はぜひ使ってみてほしい。
神社仏閣の情報はOpenStreetMapのOverpassAPIというAPIサーバーへリクエストして取得する。以下リンク先にリクエストの送り先を示すエンドポイント一覧が書いてあるので、そこに指定のフォーマットでリクエストを送る。
・Overpass APIの説明
https://wiki.openstreetmap.org/wiki/JA:Overpass_API
さて、ここまで聞いて「じゃあどうやって作るのよ」という声が聞こえてくると思うのだが、ここまで決まったのならばChatGPTに聞けば一瞬で答えを生成してくれる。
何か作るとき、その方針と解決手法を決めるまでが一番難しいと思うのだが、そこさえ決めてしまえば「時間をかけたら解決するタスク」に分割できるので、タスク化したものはどんどんChatGPTに頼っていく。タスクの言語化という割と高度な作業が残されているので人間はまだまだ仕事はなくならないのだが。
まずはWebアプリを表示する土台のHTMLを書いてもらう。その後で、SNSからどのように見えるかサムネ設定するOGPカード設定や検索エンジン向けのページ内容説明文章などのmetaタグを追記していく。
<!DOCTYPE html>
<html lang="ja">
<head>
<!-- この部分に表示指示やOGP設定、サイトの説明など大量のmetaタグを書く -->
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.7.1/dist/leaflet.css" />
<link rel="stylesheet" href="./styles.css" />
<script src="./dist/main.js"></script>
</head>
<body>
<!-- トップバナーのコンテナ -->
<div class="top-banner">
<div class="logo-container">
<img src="logo.png" alt="自分だけのパワースポットを探そう 結界発見" class="logo">
<a href="https://hothukurou.com/" class="author-link">作者の他のアプリも見てみる</a>
</div>
<div class="ad-container">
<!-- ここに広告を貼る -->
</div>
</div>
<!-- マップを表示するコンテナ -->
<div class="map-container">
<!-- id="map"のdiv要素に地図が表示される -->
<div id="map"></div>
</div>
</body>
</html>
ライブラリの使い方もChatGPTに聞けばなんとかなる。私はTypeScriptというJavaScriptに型定義を追加した言語をよく使うのだが、それを用いてクリックした地図の緯度経度を返すプログラムが以下となる。シンプル!
import * as L from 'leaflet';
// htmlロード完了後に実行
window.onload = () => {
// 最初に見せる緯度経度を指定して<div id="map">に表示
const map = L.map('map').setView([35.6895, 139.6917], 10);
// OpenStreetMapからタイルを設置する(s,z,x,yはLeafLetがよしなに置き換えてくれる)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
attribution: '© OpenStreetMap contributors'
}).addTo(map);
// 地図上でクリックした点の緯度経度を取得するクリックイベント
map.on('click', (event: L.LeafletMouseEvent) => {
const { lat, lng } = event.latlng;
});
};
ここまで下準備ができたので、後はクリック地点に「五芒星生成」などのボタンを表示するコンテナDOMを生成して、クリック地点の周辺情報を探索できるようにする。
いよいよOverPassAPIサーバーへ指定した緯度経度から距離 radiusKmまでの施設を取得するリクエストを書く。どうやって取得するコード書けばいいかわからない!という人もChatGPTを使えばすぐ出てくるだろう。
ちゃんと説明すると、サーバー宛のURLの後ろに?とつけると、後ろのデータを送ることができるのでdata={ほしいデータ情報}と書いている。このようにURLに情報を追加してリクエストを送る方法をGETメソッドと呼ぶ。
// 施設カテゴリーごとのフィルター
const getAmenityFilter = (category: string) => {
switch (category) {
case 'temple':
return '[amenity=place_of_worship]';
case 'cafe': // "cafe"
return '[amenity=cafe]';
case 'bank':
return '[amenity=bank]';
}
}
// 送信するデータのフォーマットを準備
const query = `
[out:json];
node
${getAmenityFilter(category)}
(around:${radiusKm * 1000},${lat},${lng});
out;
`;
const url = `https://overpass-api.de/api/interpreter?data=${encodeURIComponent(query)}`;
// 以下fetchでAPIサーバーにリクエストするが、繋がらなかった時はエラーになるのでtryの中で呼び出している
try {
const response = await fetch(url);
// json形式で来ているはずなのでデコードする
const data = await response.json();
}catch(e){
console.log("通信できませんでした。");
}
今回は神社仏閣を主に取得したいため、ヒット数の多かった教会などは建物名でフィルタリングして外すようにしているのだが、名称判断ではどうしてもフィルタ漏れがでてきてしまう。これには限界があるので、ある程度試行錯誤したのちに諦めることにした。
さて、ちゃんと周囲の建物データを取れることがわかったので、いよいよ五芒星・六芒星の探索アルゴリズムを書いていく。ChatGPTにアルゴリズムを提案させたのだが、全然よい提案が返ってこなかったので、ここは自力で考えた探索アルゴリズムを提唱する。
五芒星・六芒星の探索アルゴリズム
いよいよここからは五芒星・六芒星の探索アルゴリズムの説明に入る。五芒星も六芒星も同じアルゴリズムで求められるので今回は五芒星で考える。
ちなみに今回は北極点方向に一つの頂点が向いている五芒星のみを考える。回転は考慮しない。これは180度回転させた逆五芒星はサタニズム(悪魔主義)になるらしいからだ。
パワースポットと銘打ってポジティブな使い方をされるアプリを目指しているのに、ネガティブな使い方をされると面倒に巻き込まれそうなのでこのような仕様にした。
そもそもにして、この世界の神様はつむじが常に北極点を向いているという前提がないと北=上という概念は生まれないはずなのだが、我々人間には理解できない事情があるのかもしれない。
実は最初公開した時のアルゴリズムが速度的にはAPIサーバーへの負荷的にもよろしくなかったので改めて書き直している。今回は更新後のアルゴリズムを記す。
まずは指定した点の緯度経度から半径n[km]の対象物を集める。本アプリではn=100[km]である。先ほどのOverPassAPIサーバーに「緯度経度が〇〇の100[km]の対象物を送って!」といえば取得できる。取得した対象物は緯度経度と建物名が入っているのだが、緯度経度をxy座標に置き換えてこの後の処理を考えていく。
次に取得した対象物について、五芒星候補となる頂点探索してグループにする。
対象物一つ一つの角度を以下式で計算して、頂点候補の角度であるか調べていく。
arctan2ってなんだよと思う人も多いかと思うのだが、二点のxy差分を引数にとると角度を返すスゴイ関数とだけ覚えておいてほしい。
ゲーム制作しているとかなりの頻度で使うことになる関数である。例えば自機に向かって飛ばす弾の角度はこれを用いる。高校で習う三角関数の応用なので、数学はしっかり勉強した方が大人になってからの手数が増えてオトクだ。
次にここで求めた角度をもとに、対象物が頂点候補の角度であるか調べていく。
以下はそれを図示したものだ。72[deg]ごとに区切られた五芒星の頂点候補5つをオレンジ色で囲った。72[deg]には許容誤差角度x[deg]で幅を持たせている。本アプリではx=5[deg]と結構余裕を持たせており、この数値は見栄えと見つかりやすさを天秤にかけて決めた。
次にこの頂点候補のグループをそれぞれグループ内で距離ソートして距離順にする。
計算量としてはこの処理が一番重くなるはずである。図では近いもの順に1,2・・・とした。
ここまで下準備を終えたらいよいよ五芒星判定を行う。
図の中で1と振られている頂点候補の5点の距離を確認して「選んだ五点は一定範囲の距離に収まっているか」を見ていく。
5点の中から最小値を見つけて、その距離に許容距離誤差y[%]かけた値の中に他の点が入っているかを判定する。
距離が遠くなれば遠くなるほど「五芒星っぽく見える一定範囲の距離」は比例して増えていくためにこのような計算をしている。本アプリではy=110[%]であるから、最小距離が10[km]であれば他の点が11[km]以内に収まっていれば五芒星となる。
以下図の赤線が最小距離である。この距離では他の点は離れすぎているため、一定範囲の距離には収まってない。
収まっていない場合は五芒星が成立しないので、最小距離の点を消去する。
そして、そのグループの順位をスライドさせたのち、もう一度頂点候補の5点の中で最小値を見つけていく。
まだ五芒星を満たす距離範囲に入らないので、最小距離の点を消していく作業を続ける。
もし頂点候補のグループの中で一つでも空のグループができた場合、探索を打ち切る。この場合、五芒星は生成できないことになる。
もし、5点が五芒星を満たす距離範囲に入っていれば探索完了となる。
角度によって5つのグループ分けしているので五芒星の線は簡単に引ける。
上→左下→右上→左上→右下→上の順に線を引くと五芒星になる。
これが指定した点を中心とした五芒星を計算するアルゴリズムである。
六芒星も同様にグループ分けを60[deg]ごとに行い、線の引く順序を変えれば簡単に求められる。
反響と振り返り
アプリURLを貼って告知するとインプレッション数が落ちる
公開後の告知も難しかった。Xでは外部リンクを貼るとインプレッション数が落ちるという報告があるのだが、実際公開時のサイトリンク付ポストのインプレッション数は公開前の告知ポストよりも5.5倍も差があった。公開前の告知ポストの方が1日早く出している分有利ではあるのだが、それを考慮したとしても差が大きい。新しいサービスやゲームを作ったとき、たいていはSNSで告知することで広がるのだと思うのだが、今後はURLを貼らずに宣伝する方法を考えた方がよいのかもしれない。
一応リプライにURLツイートをするという作戦もあるのだが、リプライは本文よりもインプレッション数がかなり落ちるため、解決策になるかは微妙なところである。(興味持ってくれる人がリプライを見るのならば問題ないかもしれないが。)
地方によっては神社仏閣の数が少ない
一部地方では五芒星・六芒星が全く結べないという報告があった。
実際神社仏閣数を調べてみると、北海道に比べて関東地方に神社仏閣がとても集中していることがわかる。北海道の中心にある大雪山国立公園から100km内を見ても99件しか神社仏閣がヒットしないのだが、東京駅100km内は東京湾が探索範囲に入っているにも関わらず6650件もあった。
最初の方で示した通り、候補となる神社仏閣数が増えると指数的に5点を選ぶ組み合わせの数が増えていくから、この対象物の差は五芒星・六芒星の発見率に大きく影響を与える。これが地方で見つけづらい原因だろう。
まとめ
以上で、選択した周囲の神社仏閣から自分だけのパワースポットを探し出すアプリ「結界発見」の振り返りを終える。
今回、1日という超短期間で作った割には想像以上のアクセス数とご好評をいただいた。
「〇〇というものを作ってみたい」というアイデアをポストをしてインプレッションなどの反応を確認するお手軽マーケットインスタイルは需要を確認するために良い手法なのではないかと思っている。
もちろんこの手法は「この人は実際に作りそうだ」という評判があってはじめて成立するので、例え需要が不明だとしても、何かしら創作して公開し続ける活動は自分のスタンスを明確にして有利に立ち回る効果がありそうだ。
ということで、今回制作したWebアプリのリンクを再度張り付けて本記事を終える。