背景
葉山町はレストランが多いものの個人経営の店が多く、今日のOpen/Closeがわかりにくい店が少なくありません。GoogleMapは情報を広く集めることを優先しており、狭いエリアのリアルタイム性を犠牲にしています。
「今日Openしている」を店主か通行人が簡単に地図上で知らせられるアプリがあれば、その問題を解決できるのではと考えて作りました。地図UIを選んだのは、レストランを探すときにGoogleMapを使う習慣があり、それに近い体験にしたかったからです。
そこから発想が広がり、転倒・事故・水道破裂・ゴミ・落とし物など、日常の些細な出来事もサインとして知らせられるアプリに育てられると気づきました。
文字情報をあえて省いているのもポイントです。テキストを排除することで、誹謗中傷などネガティブな用途に使いにくくなるのではないか、という仮説からきています。
機能(MVP)
- 地図表示:MapLibreで現在地周辺の地図を表示します
- サイン投稿:地図をタップした地点にサインを立てられます
- サインの種類(7種類):緊急・事故・落とし物・ウンチ注意・オープン・クローズ・質問
- リアルタイム同期:Supabase Realtimeで投稿がすぐ他の端末に反映されます
- 認証:Supabase Authでログイン有無により投稿可否を制御します
- 有効期限:サインに
expires_atを設定して自動的に消滅させます
将来構想(夢想レベルも含む)
- アクセス制御:基本はローカル限定だが、申請して承認された外部ユーザーも使える仕組み
- 盛り上がりの可視化:街のイベントで多くのサインが集まると、波紋が広がるようなアニメーションで盛り上がりを表現する → 実装しました(次のセクション)
- 環境保護への応用:植物が発する危険サイン、倒木、海藻の枯死など、自然や動植物が「サインを発信する」存在として参加できれば、環境保護の文脈でも活用できるかもしれません。言葉がないUIがここでも活きる可能性があります
盛り上がりの可視化「アガるぜ!」
将来構想に書いていた「波紋で盛り上がりを表現する」を実装しました。地図をタップすると、その地点から波が広がります。強度は5段階あり、みんなで連打するほど大きな波になります。
最初はリングが広がるだけのアニメーションとして作ったのですが、実際に動かしてみると思っていたイメージと違いました。欲しかったのは「演出としての波紋」ではなく、物理的に振る舞う波だったのです。
そこで方式を変えて、2次元の波動方程式をその場で解く実装にしました。地図に重ねた1枚のCanvasに高さ場のグリッドを張り、毎フレーム波の伝播を計算して描いています。この方式にすると、干渉・反射・うねりといった挙動を個別に作り込む必要がありません。すべて計算から自然に出てきます。実際、2箇所を続けてタップすると波同士が交差し、山と山が重なった場所は明るく光り、山と谷がぶつかった場所は打ち消し合います。
波の高さはRGBの3チャンネルで別々に計算しているので、色の違う波はチャンネルごとに独立して干渉します。山は強度に応じた色で光り、谷は薄い青。水滴が落ちた水面のような見た目になりました。
技術的なポイント
Next.js(App Router)にMapLibre GLで地図表示、認証・DBはSupabase(Auth + PostgreSQL)、リアルタイム同期にSupabase Realtimeを使っています。
波紋の配信にはSupabase RealtimeのBroadcastを使い、DBには一切書いていません。一時的な演出でしかないうえ、連打される前提の機能なので、記録に残す必要がないという判断です。
波を地図に重ねる部分は、Canvasのグリッドを画面座標ではなく地理座標(緯度経度)の範囲で定義しているのがポイントです。毎フレーム画面座標に投影して描くので、地図をズーム・パン・回転させても波が地面に貼り付いたまま追従します。
この「地図の上にCanvasを重ねて毎フレーム投影する」土台ができたことが、今回いちばんの収穫かもしれません。この上でなら描画方式は差し替え自由で、波動場の次に流体シミュレーションを載せることもできそうです。
現状
Vercelにデプロイして本番公開しました(記事上部の「開く」ボタンから開けます)。地図の表示、サインの投稿、複数端末へのリアルタイム反映、波紋の共有まで、ひととおり動いています。
まだ試作段階なので、認証まわりや公開範囲の設定は今後変わる可能性があります。当面は葉山のレストラン文脈で実際に使ってみて、地域の人に触ってもらいながら育てていきたいと考えています。