日本の国政選挙において,各党・候補者の街頭演説場所を地図上にリアルタイムで可視化する Web アプリケーションである. ユーザーは,現在地や指定した日時に開催される演説情報を直感的に把握できる.本システムは,情報の収集から可視化までを自動化し,有権者が政治活動に触れる機会を創出することを目的としている.
-
🗺️ インタラクティブな地図表示
- MapLibre GL JS を採用し,ベクタータイルを用いた高速かつ滑らかな地図操作を実現している.
- GPS を利用した現在地表示機能により,ユーザー周辺で開催される演説を素早く確認できる.
- 政党ごとに定義されたブランドカラーでマーカーを色分けし,開催場所を一目で判別可能にしている.
- ピンをクリックすると詳細な演説情報(弁士,時間,出典元へのリンク)が表示される.
- 地図上の表示領域(Viewport)内の演説のみをリストに動的に反映する.
-
🕒 高度な時間軸操作とフィルタリング
- タイムスライダー: 08:00 から 20:00 までの時間帯を直感的に操作できるスライダーを搭載している.選択した時刻の前後 60 分間に開催される演説のみを地図上に表示する.
- 自動再生機能: 再生ボタンを押すと時間が自動的に進み,演説場所の移動や一日の流れ(人の動き)をアニメーションとして視覚的に追跡できる.
- 終日表示モード: 「終日」ボタンにより,特定の時刻に関わらずその日に開催されるすべての演説を一括表示できる.
- 現在時刻ショートカット: 「現在」ボタンにより,即座に現在時刻のフィルタリング状態に戻ることができる.
-
🤖 自動データ収集 (スクレイピング) パイプライン
- 主要政党(自由民主党,日本維新の会,国民民主党,日本共産党,チームみらい等)の公式サイトから,Playwright を用いて演説スケジュールを定期的 (1 時間ごと) に自動取得する.
- 取得した非構造化データ(HTML テキスト)から,正規表現や DOM 解析を用いて日時,弁士名,場所を抽出する.
- 抽出された住所情報に対して Google Places API を用いてジオコーディングを行い,正確な緯度・経度をデータベースへ保存する.
-
🔍 柔軟な検索と絞り込み
- 政党フィルタ: 特定の政党のみを地図・リストに表示する絞り込み機能.
- フリーワード検索: 候補者名や応援弁士名によるリアルタイム検索機能.インクリメンタルサーチに対応し,データ件数順に候補をサジェストする.
- Framework: Next.js 14 (App Router)
- Language: TypeScript
- State Management: Zustand (UI 状態とドメインデータの一元管理)
- Styling: Chakra UI (コンポーネント指向のスタイルシステム)
- Icons: Lucide React
- Map Library: MapLibre GL JS (WebGL ベースの地図レンダリング)
- Maps Provider: OpenStreetMap (Raster/Vector Tiles)
- Runtime: Node.js (Next.js Server Actions)
- Database: PostgreSQL
- ORM: Prisma (型安全なデータベース操作)
- Scraping: Playwright (ヘッドレスブラウザによる動的コンテンツの取得)
- Job Scheduling:
node-cron/ Next.js Instrumentation (定期タスクの実行) - Geocoding: Google Maps Platform (Places API Text Search)
- Containerization: Docker, Docker Compose
- Linter/Formatter: Biome (高速なコード解析とフォーマット)
- Task Runner: mise
- Type Check: TypeScript Compiler (tsc)
本システムの中核となるデータ収集は,以下のステップで実行される.
- Crawl: 政党ごとのスクレイパー (
src/lib/server/scraper/parties/*.ts) が起動し,対象の URL にアクセスする. - Parse: HTML 構造を解析し,演説の「日時」「場所(住所・名称)」「弁士」を抽出する.表記ゆれ(例:「13時」と「13:00」)は正規化処理によって統一される.
- Geocode: 抽出された場所情報に対し,Google Places API を用いて緯度・経度を取得する.API コール数を節約するため,既知の場所はキャッシュを利用する.
- Upsert: データベース (
speechesテーブル) に対し,複合ユニークキーを用いてデータの新規登録または更新を行う.
useStore.ts において,アプリケーション全体の以下の状態を一元管理している.
- ドメイン状態: 取得した演説データ (
rawSpeeches),フィルタリング済みの演説データ (speeches),政党データ (parties). - UI 状態: 現在選択されている時刻 (
selectedTime),選択中の演説 ID (activeSpeechId),ローディング状態 (isLoading). - フィルター状態: 日付モード (
dateMode),終日フラグ (allDay),選択された政党・人物 (filter).
地図コンポーネント (MapView) やリストコンポーネント (SpeechList) は,このストアの状態を購読し,フィルタリング条件が変更されると即座に再レンダリングを行う Reactive な設計となっている.
- ズーム制御: リストアイテム選択時には
fitBoundsメソッドを用いて該当のピンへスムーズにズームインする.ピンが 1 つのみの場合は,アニメーション開始前に Tooltip を表示させることで,ユーザーの視線を誘導する. - レイヤー管理: 政党ごとのマーカーだけでなく,選択された演説に関連する移動ルート(矢印)を動的に描画する機能を備えている.ルート情報は GeoJSON 形式で生成され,MapLibre の
addLayer/addSourceを用いて描画される.
src/
├── actions/ # Server Actions (データベース操作,外部 API 連携のエントリーポイント)
├── app/ # Next.js App Router ページ定義,グローバルレイアウト
├── components/ # UI コンポーネント群
│ ├── MapView.tsx # 地図描画・操作のコアロジック
│ ├── TimeSlider.tsx # 時間軸操作・アニメーション制御
│ ├── SpeechList.tsx # 演説情報のリスト表示
│ ├── FilterPanel.tsx # 絞り込み条件の設定パネル
│ └── ...
├── lib/ # 共有ライブラリ,ユーティリティ
│ ├── api.ts # フロントエンド用 APIラッパー
│ ├── prisma.ts # Prisma クライアントのシングルトン
│ └── server/ # サーバーサイド専用ロジック
│ ├── geocoding.ts # ジオコーディング処理
│ └── scraper/ # 政党別スクレイパーの実装
├── store/ # Zustand ストア定義 (useStore.ts)
└── types/ # TypeScript 型定義 (ドメインモデル,API レスポンス型)本サービスは非営利の個人プロジェクトであり,情報の正確性について一切の保証を行わない. サービスを利用する際は,必ず以下の点に同意したものとみなす.
- 提供される情報は各政党の公式サイト等から自動収集したものであり,急な変更や誤りが含まれる可能性がある.
- 本サービスを利用したことによる損害について,開発者は一切の責任を負わない.
- 本サービスは Google Maps Platform のデータを利用している.
mise(推奨),Node.js,pnpm,および Docker がインストールされていること.
- 依存関係の導入:
mise install mise setup
- 環境設定:
.env.sampleを.envに複製し,以下の変数を設定する.DATABASE_URL: PostgreSQL 接続文字列GOOGLE_PLACES_API_KEY: Google Maps Platform API キー
- ローカルサーバーの起動:
上記コマンドにより,Docker コンテナ (DB) の起動,Prisma マイグレーションの適用,Next.js 開発サーバーの立ち上げが一括で行われる.
mise dev
- アクセス:
ブラウザで
http://localhost:3000/enzetsu-naviを開く.
GitHub Actions を基盤とした CI/CD パイプラインを構築している.
main ブランチへのプッシュをトリガーとして,Docker イメージのビルド,コンテナレジストリへのプッシュ,およびサーバーへのデプロイが自動的に実行される.