サイクリングルートを走る前にプレビューできたらと思ったことはありませんか?Party Onbiciの全てのパーティールートに対して美しい上空飛行動画を生成する自動化システムを構築し、ライダーに旅で何が待っているかの映画的なプレビューを提供しています。

課題

サイクリングイベントを企画したり参加したりする際、ルートを理解することは非常に重要です。静的な地図は役立ちますが、実際にルートを走る体験を伝えることはできません。ライドにコミットする前に、ルートを仮想的に「飛行」できる方法をユーザーに提供したいと考えました。

私たちのソリューション:ヘッドレス動画レンダリング

Puppeteerを使用してヘッドレスChromeブラウザを実行し、インタラクティブなMapLibre GLマップ上でルートをレンダリングし、仮想カメラがパスに沿って飛行する間にフレームをキャプチャするNode.jsサービスを構築しました。仕組みは以下の通りです:

1. ルート補間

サイクリングルートには数千の座標点がある場合があります。15秒間30FPSのスムーズな動画を作成するには、正確に450フレームが必要です。距離ベースの補間を使用してルートを均等にサンプリングします:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Calculate cumulative distances using Haversine formula
const distances = [0];
for (let i = 1; i < coordinates.length; i++) {
  const dist = haversineDistance(coordinates[i-1], coordinates[i]);
  totalDistance += dist;
  distances.push(totalDistance);
}

// Sample at exactly totalFrames points
for (let frame = 0; frame < totalFrames; frame++) {
  const targetDist = (frame / (totalFrames - 1)) * totalDistance;
  // Interpolate position at this distance...
}

2. スムーズな方位によるカメラアニメーション

単純に進行方向にカメラを向けると、曲がりくねったルートでぎくしゃくした動きが発生します。360度/0度のラップアラウンドの特別な処理を含め、12フレームの移動平均を使用してカメラの方位をスムーズにします:

1
2
3
4
5
6
7
// Use sin/cos components for circular averaging
for (let j = i - smoothWindow; j <= i + smoothWindow; j++) {
  const rad = rawBearings[j] * Math.PI / 180;
  sinSum += Math.sin(rad);
  cosSum += Math.cos(rad);
}
const avgBearing = Math.atan2(sinSum/count, cosSum/count) * 180 / Math.PI;

3. MapLibre GLレンダリング

ルートは美しいStadia Mapsベースレイヤー上に以下の要素でレンダリングされます:

  • 視認性のための白いアウトライン付きのハイライトされたルートライン
  • アニメーション化されたカメラ位置インジケーター
  • スタート(緑)とゴール(赤)マーカー
  • 都市部向けのオプションの3D建物押し出し

4. フレームキャプチャと動画エンコード

Puppeteerは各フレームをPNGスクリーンショットとしてキャプチャし、ffmpegを使用してVP9コーデックでWebM動画にエンコードします:

1
2
3
ffmpeg -framerate 30 -i frame_%05d.png \
  -c:v libvpx-vp9 -crf 20 -b:v 0 \
  route.webm

技術アーキテクチャ

1
Django App → Celery Task → Node.js Renderer → S3 Storage
  1. Djangoが動画生成をトリガー:パーティーが作成または更新されたとき
  2. Celeryがタスクをキューイング:非同期処理用
  3. Node.jsレンダラー:MapLibre GLでPuppeteerを実行
  4. ffmpegがエンコード:キャプチャされたフレームを
  5. 動画がアップロード:S3に、パーティーレコードが更新される

パフォーマンスの考慮事項

解像度長さレンダリング時間ファイルサイズ
1280x72015秒60-90秒2-4 MB
1920x108015秒90-120秒4-8 MB

本番環境では、GPU依存を避けるためにソフトウェアレンダリング(SwiftShader)を使用したDockerコンテナを使用します:

1
2
--use-angle=swiftshader
--enable-unsafe-swiftshader

追加した機能

  • カスタマイズ可能な動画サイズ - ソーシャルメディア共有用の正方形フォーマット
  • ロゴ透かし - 組織のロゴで動画をブランディング
  • カメラの傾斜とズーム - 視野角と高度を調整
  • ルートラインスタイリング - カスタムの色と幅

Django統合

アプリケーションコードから、動画の生成は以下のように簡単です:

1
2
party = Party.objects.get(uid="...")
party.generate_video(width=1080, height=1080, duration=10)

レンダリングが完了すると、動画URLが自動的に更新されます:

1
2
3
4
5
{% if party.video_url %}
<video controls>
  <source src="{{ party.video_full_url }}" type="video/webm">
</video>
{% endif %}

次のステップ

いくつかの機能強化を検討しています:

  • リアルタイムプレビュー - ユーザーがルートを描画する際のライブレンダリング
  • 複数のカメラアングル - サイドビュー、オーバーヘッドショット
  • 天気オーバーレイ - ルートに沿った予報状況を表示
  • 標高プロファイル - 動画内で上り坂と下り坂を可視化

ルート上空飛行動画は最も人気のある機能の1つとなり、サイクリストがどのライドに参加するかについて情報に基づいた決定を下すのに役立っています。現代のWeb技術の組み合わせにより、高価な制作作業であったものが、自動化されたオンデマンドサービスに変わりました。


試してみたいですか?パーティーを作成すると、ルート動画が自動的に生成されます!