私は、友達の紹介でさどんでこプロジェクトのIT担当として様々システム構築などを行っています。今回、スタンプラリー作成でかなり大変だったということから、開発秘話をブログにまとめてみました。興味ある方ぜひ御覧ください。

第1,2,3話は以下です、合わせてお読みください。



今回のテーマ
- LIFF(LINE Front-end Framework)で位置情報を取得
- ユーザーがスタンプ範囲に入ったら、自動でスタンプ獲得
- スマホ対応にこだわったUI
「GPSでスタンプを押したい!」という課題
鬼太鼓スタンプラリーでは、現地に行くことでスタンプを獲得できるようにしたい。
ただし、LINE Bot単体ではユーザーの現在地を取得することはできません。
そこで着目したのが、LIFF(LINE Front-end Framework)です。
LIFFとは?LINE × Webの架け橋
LIFFを使えば、LINEの中でWebアプリを動作させることができます。
- LINEユーザーとしてWebアプリを開く
- スマホのブラウザAPI(GPSなど)にアクセスできる
- Web上の判定結果をLINE Botと連携できる
この特性を活かして、LIFF → 位置情報取得 → 範囲内ならスタンプ獲得というフローを構築しました。
スタンプ獲得の流れ
1. LINEのリッチメニューで「GPSをよみこむ」をタップ
2. LIFFアプリ(Web)が起動
3. スマホのGPSから現在地を取得
4. Django APIに現在地を送信
5. 範囲内にあるスタンプがあれば、自動でスタンプ獲得処理が行われる
6. 「スタンプ獲得:{スタンプ名}」メッセージを画面表示
すべてがユーザーのワンタップで完結し、位置情報を活用した自然な体験が実現しました。

GPSをよみこむをタップ

現在地でスタンプを探すをタップ

近くのスタンプを探し、あれば自動で獲得する
範囲判定のロジック
スタンプラリーで重要なのが「ユーザーが本当にその場所にいるかどうか」。
これを判定するために、以下のような処理をサーバー側(Django)に実装しました。
1. 地球上の距離を求める「haversineの公式」
ユーザーの現在地(緯度・経度)と、各スタンプスポットの位置との距離を比較します。
距離の計算には、球面上の2点間の距離を求める有名な公式「haversine(ハバーサイン)」を使います。
from math import radians, sin, cos, sqrt, asin
def haversine(lat1, lon1, lat2, lon2):
# 度をラジアンに変換
lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2])
# haversineの公式による距離計算
dlat = lat2 - lat1
dlon = lon2 - lon1
a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
c = 2 * asin(sqrt(a))
r = 6371000 # 地球の半径(メートル)
return c * r
この関数で、スポットとの**実際の直線距離(m)**が取得できます。
2. LINEログインの正当性を確認(セキュリティ)
LIFF経由で送られてくるid_tokenを、LINEのAPIを使って検証します。
res = requests.post(
"https://api.line.me/oauth2/v2.1/verify",
data={
"id_token": id_token,
"client_id": settings.LINE_CHANNEL_ID
},
headers={"Content-Type": "application/x-www-form-urlencoded"}
)
line_id = res.json().get("sub")
- id_tokenはLIFFでのLINEログイン時に取得
- LINEの公式APIを叩いて正当性チェック
- LINEのユーザーID(sub)を取得し、DjangoのUserと紐づけ
3. 各スタンプスポットと距離を比較して判定
for spot in StampSpot.objects.all():
dist = haversine(lat, lon, spot.latitude, spot.longitude)
if dist <= spot.radius_m:
_, created = UserStamp.objects.get_or_create(user=user, stamp=spot)
return JsonResponse({
"status": "ok",
"acquired": created,
"stamp_name": spot.name,
"stamp_image_url": spot.image.url if spot.image else ""
})
- 登録されているすべてのスタンプスポットに対して、
- haversine() で現在地との距離を算出
- spot.radius_m以内であれば、獲得処理を実行
- すでに獲得済みならacquired: falseで返す
このようにして、実際にその場所に来た人だけがスタンプを獲得できるようになっています。
レスポンスの例(範囲内で新規獲得)
{
"status": "ok",
"acquired": true,
"stamp_name": "両津港",
"stamp_image_url": "https://.../ryotsukou.png"
}
レスポンスの例(範囲外)
{
"status": "no_match"
}
このように、LIFFから送られてくる位置情報をサーバー側で精密に検証し、正確でセキュアなスタンプ獲得体験を提供しています。
UIにもこだわったLIFFページ ~「押す快感」と「達成感」を1画面で~
「現地でGPSを使って、ただスタンプをもらえるだけじゃ面白くない」
スタンプラリーの体験を”ワクワクする瞬間”に昇華させるため、LIFFページのUI・演出に特に力を入れました。
Bootstrapでつくるシンプルなレイアウト
- 全体はBootstrap 5をベースに構築
- ボタン、メッセージ表示、レスポンシブ対応を簡潔に実装
- containerで整った中央配置、alertで状態を直感的に伝達
- ユーザーが迷わず1ボタンで完了する導線を追求
<button id="check-location" class="btn btn-primary w-100 mb-3">
現在地でスタンプを探す
</button>
<div id="message" class="alert alert-secondary" role="alert">
👆 上のボタンをタップして、スタンプを探してください。
</div>
ユーザーに迷いがないよう、「今やること」「結果」「次の動作」を視覚的に区切って表示するよう設計しました。
スタンプ獲得のアニメーションは「体験のハイライト」
LIFFページのこだわりの1つは、スタンプを獲得したときのアニメーション演出です。
ポイント:
その後は控えめな「確認メッセージ」で余韻を残す
画面中央に、スタンプ画像をポップアップ表示
@keyframes pop を使用した拡大・回転アニメーション
アニメーション終了後、獲得メッセージがふわっと表示
@keyframes pop {
0% {
transform: scale(7) rotate(-20deg);
opacity: 0.8;
}
100% {
transform: scale(1) rotate(0deg);
opacity: 1;
}
}
stampImg.style.animation = "pop 2s ease-out forwards";
演出の流れ:
- スタンプ画像が中心にドーンと出現(scale 7 → 1)
- 数秒間そのまま表示
- 獲得メッセージを表示
- 自動で非表示 → 完了メッセージへ遷移
ユーザーに「やった!スタンプ獲得した!」という視覚的な達成感を届ける工夫です。
スマホ1つで鬼太鼓スタンプラリーが完成!
この仕組みによって、ユーザーは:
- アプリインストール不要
- 現地に行ってワンタップでスタンプ獲得
- 位置を自動判定してくれる、直感的な体験
- スタンプカードにスタンプが貯まる喜び
をLINE上でそのまま味わえるようになりました。
次なる展望:位置情報 × Web体験の拡張へ
このLIFF連携が完成したことで、スタンプラリーは「現地体験」と「デジタル収集」が完全にリンクした形になりました。
次はこんなアイデアも視野に入れています:
- 獲得数に応じた称号やバッジの導入
- 全スタンプ制覇で限定動画が視聴可能
- 他地域にも広げた鬼太鼓ラリーの全国展開
開発秘話、これにて完結!
4話にわたりお届けした開発裏話、いかがでしたでしょうか?
WordPressから始まり、LINE × Django × LIFFという独自スタイルにたどり着いたこのプロジェクト。
技術だけでなく、地域への想いを形にする手段としての開発の魅力も伝えられたら嬉しいです。
あと、ぜひ鬼太鼓スタンプラリーに参加してください!!

コメント
コメント一覧 (1件)
[…] 第4話:位置情報でスタンプ獲得!LIFFとLINE Botの連携の裏側 今回のテーマ LIFF(LINE Front-end Framework)で位置情報を取得 […]