位置情報を取得表示してSNSにも投稿できるウェブアプリ作り

ソフトウェア開発部の川谷です。4月から半年ほど業務整理のため並行して行っていた仕事が無事完結し、この10月からあどすぺ一本で活動できる態勢が整いました。引き続きよろしくお願いいたします。

このブログ記事でIT関係充実させていこうということで、まずは最近作った簡単なウェブアプリの紹介とその構築過程を紹介します。

作るウェブアプリ → 位置情報取得表示 & 投稿アプリ

この記事では、スマートフォンなどの位置情報(GPSやWiFi、Bluetoothなどで計測)を取得できるGeolocation APIを利用して、自分の位置情報を表示したり、あらかじめ登録した地点から最寄りのスポットを求め、SNSに投稿できるウェブアプリを作ります。

このアプリを作った理由

 ひとつはJavaScriptから利用できるAPIを使う練習です。もうひとつは、自分が愛用していたサービスがX(Twitter)の仕様変更に巻き込まれて少し不便になったので、その代わりになりそうなものを作りたかったというものがあります。

 私は旅行が好きです。特に鉄道・バス・航空機を利用して行くのが好きです。旅行の時、行った場所の写真だけでなく位置情報も記録・共有したくなります。同じようなことを考える人は世界中にいるようで、様々なアプリ・ウェブサービスが作られています。そのひとつが「Foursquare Swarm」です。私をはじめ、旅行が好きな友人がこのアプリを利用しています。すでに登録されていたり、自分で新規登録したスポットにチェックインして、Swarmで友達関係にあるユーザーに共有できるほか、SNS共有にチェックを入れた状態でチェックインすると、Twitter(当時)やFacebookに同時投稿できます。

 ……いえ、Twitter → Xに関しては同時投稿ができなくなりました。X(Twitter) APIの仕様変更・利用規約変更のため、SwarmからXへ直接投稿することができなくなってもう4か月くらい経ちます。アプリに実装されている他のアプリとの共有機能を使うとXアプリを経由して投稿できるのですが、Android版だとその共有時の文言が少々エレガントではない不思議な翻訳文になってしまいます。iPhone版は同時投稿できていた時と同じ文言が出てくるので、おそらく2つに分かれている開発チームの思想が異なるのかもしれません。

 不満が生まれたり、物足りなさを感じたりした時こそがものづくりのチャンスです。そこで、私は似たような機能を実現できるウェブアプリを作ろうと思いました。

アプリに実装する機能

 Swarmで自分が利用していた機能をリストアップし、それらの実装を目指します。

  • 自分が今いる位置から最も近いスポットを表示する。
    • スポット情報は自分の手でファイルに書く単純な形にする。(登録用画面の実装はしない。)
  • スポットの名前などをSNSに投稿する。
    • SNSへの共有投稿は、各SNSが共有投稿用に用意しているURLを使う形で画面を出す。
  • 地図も表示できるようにする。

利用するプログラミング言語や技術

  • ウェブページ作成
    • HTML / CSS
    • JavaScript
  • Web API
    • Geolocation API(位置情報API)
  • JavaScriptライブラリ
    • leaflet.js(地図表示用の外部ライブラリ)

 各技術の利用にあたっては、MDN Web Docs(https://developer.mozilla.org/ja/)のリファレンスを参考にしました。

 作成したウェブアプリを実際に動かすために、ウェブホスティングサービスを利用します。今回のウェブアプリはクライアント側で完結し、サーバー上で別途処理用のプログラムを動かす必要がありませんので、どのレンタルサーバーを利用しても良いですし、AWS S3やGoogle Firebase Hosting、GitHub Pages、Netlifyを利用した静的サイトホスティングサービスでも動かすことができます。

ウェブアプリの公開先

 今回のウェブアプリを、私の公的活動用GitHubアカウントを利用する形でGitHub Pagesの機能により公開しました。

https://t-kawa-banpeiyu.github.io/nowpos-app/

 ウェブアプリの全体のソースコードも、当GitHubアカウントのリポジトリに公開しています。(自分の私的活動用アカウントからリポジトリを移籍させました。)

https://github.com/t-kawa-banpeiyu/nowpos-app

作り方と各要素の解説

このウェブアプリの重要な部分を、次の要素に分けて解説します。

  • 位置情報取得
  • 地図表示
  • 最寄りの登録スポットを検索
  • SNS共有

HTMLを記述してウェブページを1つ作り、CSSで地図を表示する画面領域とボタンを整え、各ボタンに対してJavaScriptを使って実行したいことを記述して結びつけていきます。今回は地図表示用のleaflet.js以外のJavaScriptライブラリは使用しませんでしたが、ライブラリに頼らずに画面制御コードを書くのは結構骨が折れますので、実務上はjQuery等のライブラリを適宜利用した方が良いでしょう。

位置情報取得

位置情報を取得するにはGeolocation APIを利用します。JavaScriptから簡単に呼び出して利用することができます。解説がMDN Web Docsの「位置情報API」、サンプルコードが同サイトの「位置情報APIの使用」にあります。

上記サンプルコードを実行すると、位置情報を取得してその位置の周辺の地図を『OpenStreetMap』(世界中の人がフリーの地形・道路情報をもとに地図を作り上げるプロジェクト)で表示するためのリンクが画面に現れます。このサンプルコードをもとに、地図表示機能などを付け加えていきます。

地図表示

地図表示には leaflet.js というJavaScriptライブラリを利用します。HTMLとCSSで指定した範囲に、JavaScriptで指定した位置情報の地図を各種地図サービスと連携する形で表示させることができます。連携できる地図には『OpenStreetMap』や、国土地理院が提供する日本国内の基礎的な地図『地理院地図』があります。

また、現在地を示すための画像や円を、leaflet.jsの機能を利用して描くことができます。コードの一部を示します。このコードだけでは動きませんので、完全版は上記ウェブアプリを実行してみてください。

// 変数positionには位置情報APIから得られた
// 緯度・経度・位置情報の精度などの値が入っている
const latitude  = position.coords.latitude;
const longitude = position.coords.longitude;
const accuracy = position.coords.accuracy;

// 現在地を示すアイコンの定義
var myPin = L.icon({
    iconUrl: 'images/pin.png',
    iconSize: [64, 48],
    iconAnchor: [32, 48]
});

// leaflet.jsを読み込み、地図の初期設定をする
map = L.map('map').setView([33.5913, 130.3989], 17);
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '© OpenStreetMap'
}).addTo(map);

// 現在地をマップ表示の中心とする
map.setView([latitude, longitude]);

// 現在地の点と位置情報精度を示す円を描画
nowPosMarker = L.marker([latitude, longitude], {
    icon: myPin
}).addTo(map);
nowPosAccuracyCircle = L.circle([latitude, longitude], {
    radius: accuracy,
    color: 'blue',
    fillColor: '#399ADE',
    fillOpacity: 0.2
}).addTo(map);

leaflet.jsを使うことで、地図のズーム設定がコード1つでできたり、点や円を描く際の面倒な座標計算をせずに、実際の緯度と経度、実際の距離が何メートルかを指定するだけで描画できたりと大変便利です。こうした有志製作のライブラリは大変ありがたいです。

最寄りの登録スポットを検索

私のウェブアプリを成り立たせるのに重要なのがこの機能です。機能実現のためには、

  • 登録スポットの一覧を作る
  • 現在地とスポット間の距離を計算する

の2点が必要となります。登録スポットの一覧は、一般にはサーバー側にリストをファイルやデータベースのテーブルの形で用意することになりますが、今回はシンプルさを重視して、この一覧をJavaScriptのコードで用意することにしました。1点注意があります。JavaScriptのコードは私達が扱う端末(パソコンやスマートフォン)にダウンロードされて実行されるため、JavaScriptのコードの形式でスポット一覧を作る際には、他の人に知られては困るスポットは登録しないでください。

スポット1つにつき、そのスポットの緯度・経度・名称・住所を登録したJavaScriptのオブジェクトを作ります。サンプルコードは次のようになります。

const landmarks = {
    "dazaifuTenmanguShrine": { "posName": "太宰府天満宮", "address": "福岡県太宰府市", "latitude": 33.5212, "longitude": 130.5348 },
    "nishitetsuFukuokaSta": { "posName": "西鉄福岡(天神)駅", "address": "福岡県福岡市中央区", "latitude": 33.5892, "longitude": 130.3994 },
    "hakataSta": { "posName": "博多駅", "address": "福岡県福岡市博多区", "latitude": 33.5899, "longitude": 130.4207 },
    "tokyoBigSight": { "posName": "東京ビッグサイト(東京国際展示場)", "address": "東京都江東区", "latitude": 35.6300, "longitude": 139.7946 },
    "fukuokaAirport": { "posName": "福岡空港", "address": "福岡県福岡市博多区", "latitude": 33.5971, "longitude": 130.4480 },
    "hanedaAirport": { "posName": "東京国際空港(羽田空港)", "address": "東京都大田区", "latitude": 35.5495, "longitude": 139.7868 },
    "otoineppuSta": { "posName": "音威子府駅", "address": "北海道中川郡音威子府村", "latitude": 44.7292, "longitude": 142.2598 },
    "fukushimaRaceCourse": { "posName": "福島競馬場", "address": "福島県福島市", "latitude": 37.7653, "longitude": 140.4804 }
};

export { landmarks };

上記のようにスポット情報を定義したとき、次のJavaScriptコードで中の情報を参照できます。

landmarks["dazaifuTenmanguShrine"].posName      // 返される値 -> "太宰府天満宮"
landmarks["dazaifuTenmanguShrine"].address      // 返される値 -> "福岡県太宰府市"

登録したスポットと現在地の間の距離を計算するのは『緯度・経度で表される2点間の距離を計算する』というタスクになります。距離計算は突きつめるとかなり難しく興味深いテーマとなりますが、簡単に計算できるようにするための関数を作って公開してくださっている方がいらっしゃいましたので、その成果を借用しました。(Yusuke Kawasaki氏作成:https://gist.github.com/kawanet/15c5a260ca3b98bd080bb87cdae57230

距離計算は、各スポットの情報全てとの間で実施します。ポイント数が増えてきたりすると計算量を削減することを考える価値が出てきますが、今回のケースで、かつスポット数が数万個のレベルなら普通に総当たりで計算しても問題は無いと考えています。

SNS共有

このウェブアプリでは、X(Twitter)・Mastodon・Misskeyの3つのSNSに対して、それぞれ情報を共有できる投稿URLを作ってアクセスします。ここで、MastodonとMisskeyは世界中に多くのサーバー(インスタンス)があり、ユーザーによってアクセス先が異なってくると思われます。本記事では説明を簡略化するために、Mastodonについては日本国内で著名なインスタンスのひとつ・ mstdn.jp を前提としてURLを出力し、また、Misskeyについても著名なサーバーのひとつである misskey.io を前提としてURLを出力します。サーバー(インスタンス)を選択可能とすることは次なる課題にしたいと思います。

各SNSには、特定のパラメーターを設定したURLをウェブブラウザ等で開くと、投稿文やリンクが入力された状態の投稿画面を出せる仕組みが用意されています。スマートフォンの設定によっては、さらに対応するアプリの画面が開いて投稿できることもあります。投稿画面で選択されるユーザーは、ウェブブラウザやアプリでログイン状態のユーザー、複数アカウントにログインしているならば、その中で現在表示するよう設定しているユーザーとなります。

サービスURL
X(Twitter)https://twitter.com/intent/tweet?ref_src=twsrc%5Etfw&url={共有したいURL}&text={共有したい文字列}
Mastodon(投稿先として mstdn.jp を例にする)https://mstdn.jp/share?text=${共有したいURLや文字列など投稿欄に表示させる文字すべて}
Misskey(投稿先として misskey.io を例にする)https://misskey.io/share?url={共有したいURL}&text={共有したい文字列}

この仕組みは、各種のウェブサイトが実装しているSNS共有ボタンの仕組みそのものです1。自作のウェブサイトでも上記のリンクを作ると共有ボタンが作れます。なお、X(Twitter)は少し前にURLの仕様(エンドポイント)を十分な告知なく表に記載した形に変更してしまったため、世の大半のX(Twitter)共有ボタンとそのボタンを設定するための記事が無効化されてしまっています。

これらを組み合わせたものが、GitHubリポジトリにアップロードしているコードと、公開しているサンプルアプリになります。登録しているスポット情報が大変偏っているため、そのままでは実用にはならないと思われます。雰囲気を感じて頂いた後は、ウェブホスティングサービス(レンタルサーバーなど)にファイル一式をアップロードして、スポット情報を書き換えて遊んでみてください。このウェブアプリはMIT Licenseを適用しているため、自由に改変・頒布が可能です。

最後に

今回のサンプルウェブアプリの発展形として、登録スポットへのチェックイン履歴を記録する機能を加えたものを自作しました。セキュリティ面を検証して、もしここで紹介しても問題なさそうでしたら、いずれまた記事を書こうと思います。