Har du någonsin önskat att du kunde förhandsgranska en cykelrutt innan du cyklar den? Vi byggde ett automatiserat system som genererar vackra överflygningsvideor för varje Party Onbici-rutt, vilket ger cyklister en filmisk förhandsvisning av vad de kan förvänta sig på sin resa.

Utmaningen

När man organiserar eller går med i ett cykelevenemang är det avgörande att förstå rutten. Statiska kartor är hjälpsamma, men de förmedlar inte upplevelsen av att faktiskt cykla rutten. Vi ville ge användarna ett sätt att virtuellt “flyga igenom” rutten innan de bestämmer sig för en åkning.

Vår lösning: Headless videorendering

Vi byggde en Node.js-tjänst som kör en headless Chrome-webbläsare med Puppeteer, renderar rutten på en interaktiv MapLibre GL-karta och fångar frames när en virtuell kamera flyger längs banan. Så här fungerar det:

1. Ruttinterpolering

En cykelrutt kan ha tusentals koordinatpunkter. För att skapa en smidig video vid 30 FPS under 15 sekunder behöver vi exakt 450 frames. Vi använder avståndsbaserad interpolering för att sampla rutten jämnt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// Beräkna kumulativa avstånd med Haversine-formeln
const distances = [0];
for (let i = 1; i < coordinates.length; i++) {
  const dist = haversineDistance(coordinates[i-1], coordinates[i]);
  totalDistance += dist;
  distances.push(totalDistance);
}

// Sampla vid exakt totalFrames punkter
for (let frame = 0; frame < totalFrames; frame++) {
  const targetDist = (frame / (totalFrames - 1)) * totalDistance;
  // Interpolera position vid detta avstånd...
}

2. Kameraanimering med jämn kurs

Att helt enkelt peka kameran i färdriktningen skapar ryckig rörelse på slingrande rutter. Vi jämnar ut kamerakursen med ett glidande medelvärde över 12 frames, med speciell hantering för 360°/0°-övergången:

1
2
3
4
5
6
7
// Använd sin/cos-komponenter för cirkulärt medelvärde
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-rendering

Rutten renderas på ett vackert Stadia Maps-baslagret med:

  • En markerad ruttlinje med vit kontur för synlighet
  • Animerad kamerapositionsindikator
  • Start (grön) och mål (röd) markörer
  • Valfri 3D-byggnadsextrusion för urbana områden

4. Frame-fångst och videokodning

Puppeteer fångar varje frame som en PNG-skärmdump, sedan använder vi ffmpeg för att koda dem till en WebM-video med VP9-codec:

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

Teknisk arkitektur

1
Django-app → Celery-uppgift → Node.js-renderer → S3-lagring
  1. Django utlöser videogenerering när ett party skapas eller uppdateras
  2. Celery köar uppgiften för asynkron bearbetning
  3. Node.js-renderer kör Puppeteer med MapLibre GL
  4. ffmpeg kodar de fångade frames
  5. Video laddas upp till S3 och party-posten uppdateras

Prestandahänsyn

UpplösningLängdRendertidFilstorlek
1280x72015s60-90s2-4 MB
1920x108015s90-120s4-8 MB

I produktion använder vi Docker-containrar med mjukvarurendering (SwiftShader) för att undvika GPU-beroenden:

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

Funktioner vi lade till

  • Anpassningsbara videodimensioner - Kvadratiskt format för delning på sociala medier
  • Logotypvattenstämpel - Märk dina videor med din organisations logotyp
  • Kameralutning och zoom - Justera betraktningsvinkeln och höjden
  • Ruttlinjestil - Anpassade färger och bredder

Django-integration

Från applikationskoden är det enkelt att generera en video:

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

Video-URL:en uppdateras automatiskt när renderingen är klar:

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

Vad kommer härnäst

Vi utforskar flera förbättringar:

  • Förhandsvisning i realtid - Live-rendering när användare ritar rutter
  • Flera kameravinklar - Sidovyer, överheadbilder
  • Väderöverlagring - Visa prognosförhållanden längs rutten
  • Höjdprofil - Visualisera klättringar och nedförsbackar i videon

Ruttöverflygningsvideor har blivit en av våra mest populära funktioner, som hjälper cyklister att fatta informerade beslut om vilka åkningar de ska gå med i. Kombinationen av moderna webbteknologier gör det som skulle ha varit en dyr produktionsuppgift till en automatiserad, on-demand-tjänst.


Vill du prova? Skapa ett party så genereras din ruttvideo automatiskt!