你是否曾希望在騎行前預覽騎行路線?我們建構了一個自動化系統,為Party Onbici上的每條派對路線生成精美的飛越影片,讓騎行者可以電影般地預覽他們旅程中將會遇到的風景。

挑戰

在組織或參加騎行活動時,了解路線至關重要。靜態地圖雖然有用,但無法傳達實際騎行路線的體驗。我們想給使用者一種方式,讓他們在承諾參加騎行之前能夠虛擬地「飛越」路線。

我們的解決方案:無頭影片渲染

我們建構了一個Node.js服務,使用Puppeteer運行無頭Chrome瀏覽器,在互動式MapLibre GL地圖上渲染路線,並在虛擬攝影機沿路徑飛行時擷取幀。以下是其運作原理:

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. 平滑方位的攝影機動畫

簡單地將攝影機指向行進方向會在彎曲的路線上產生抖動。我們使用12幀的移動平均來平滑攝影機方位,並對360度/0度的環繞進行特殊處理:

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

在生產環境中,我們使用帶軟體渲染(SwiftShader)的Docker容器來避免GPU依賴:

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

我們新增的功能

  • 可自訂影片尺寸 - 用於社群媒體分享的正方形格式
  • Logo浮水印 - 用您組織的Logo為影片添加品牌標識
  • 攝影機傾斜和縮放 - 調整視角和高度
  • 路線線條樣式 - 自訂顏色和寬度

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 %}

下一步計劃

我們正在探索幾項增強功能:

  • 即時預覽 - 使用者繪製路線時進行即時渲染
  • 多攝影機角度 - 側視圖、俯視鏡頭
  • 天氣疊加 - 顯示沿路線的預報條件
  • 海拔剖面 - 在影片中視覺化上坡和下坡

路線飛越影片已成為我們最受歡迎的功能之一,幫助騎行者做出明智的決定,選擇參加哪些騎行活動。現代Web技術的結合將原本昂貴的製作任務轉變為自動化的隨選服務。


想試試嗎?建立一個派對,您的路線影片將自動生成!