BilligtFrem product mockup

Journey planner comparing train, coach, ferry and flight prices across Denmark.

Role
Co-builder (frontend, map and infrastructure)
Client
VOA (own product)
Timeline
Feb 2026 — Now

(01) Overview

BilligtFrem is a journey planner for travelers in Denmark. One search can cover trains, coaches, ferries, domestic flights, walking and driving, combines them into door-to-door routes and compares them on total price, including every mandatory charge it knows of. It is VOA's own product, which I build together with Aske Gudmand-Høyer.

Details
Role
Co-builder (frontend, map and infrastructure)
Client
VOA (own product)
Timeline
Feb 2026 — Now
Team
Victor + Aske Gudmand-Høyer
Platform
Web
Stack
SvelteKit TypeScript MapLibre GL Tailwind CSS Docker OpenTelemetry

The problem

Getting around Denmark can mean choosing between a train, a coach, a ferry or a short domestic flight, each from a different operator with its own site and prices. Comparing them means searching each one, joining connections by hand and adding up the fees yourself.

The outcome

BilligtFrem is live at billigtfrem.dk. One search checks up to ten providers and ranks the combined routes as Cheapest, Best and Fastest. I built the map, most of the search and results UI, the self-hosted OSRM stack and OpenTelemetry tracing; Aske owns the provider integrations, route engine and pricing.

(02) Results
10 Providers compared, including walking and driving
20 Recorded flight tracks, one per direction
3 Self-hosted OSRM routing profiles for Denmark
(03) Key features
Routes drawn on the map 01

Routes drawn on the map

Each result draws itself from origin to destination, leg by leg, in about 0.9 seconds. The selected route is drawn in color over its gray line, estimated paths are dashed, and flights show a plane on the line.

Filter chips and sort tabs 02

Filter chips and sort tabs

Sort tabs show the cheapest, best and fastest winner up front, filter chips narrow by ways to travel, changes, departure time and price, and each row opens in place with its itinerary, price parts and buy buttons.

Plan your trip 03

Plan your trip

The start page pairs a short trip form with popular trips and a map of Denmark. Hovering a popular trip frames it on the map, and clicking one searches it with the visitor's chosen time and travelers.

Choose providers and costs 04

Choose providers and costs

The search bar's provider picker sets which providers to search, the value of time (kr/h) the Best tab weighs against price, and a driving cost (kr/km). The results header counts providers checked; the footer names any that found nothing.

(04) Design choices

Four decisions that shaped it, and what each one cost.

  1. Flight paths from recorded flights

    The choice
    Flight legs on covered airport pairs approximately follow a recorded flight: the medoid per route and direction, the one closest on average to all others, from ADS⁠-⁠B Exchange's monthly sample data, simplified with Douglas⁠–⁠Peucker. They're drawn solid with a plane, not dashed like other estimates.
    The trade-off
    Tracks are static: a new city pair draws as a fallback arc until one is added, and one route rests on a single flight.
  2. One animation clock per route

    The choice
    Results stream in. Each route gets its own start time, shared by its gray line and its colored overlay, so late arrivals animate in while routes already on the map stay put when the ranking or selection changes. Reduced motion skips the animation.
    The trade-off
    MapLibre can't animate a line in each route's own color, so line shapes are recomputed on every frame while drawing.
  3. Routing graphs baked into images

    The choice
    First and last⁠-⁠mile legs, and paths a provider doesn't supply, are routed by self⁠-⁠hosted OSRM for Denmark. The car, bicycle and foot graphs are prepared at Docker image build, so containers start serving at once, and the healthcheck runs real routes on all three.
    The trade-off
    Fresh OpenStreetMap data needs an image rebuild with the cached extract cleared, not just a container restart.
  4. Show only searched prices

    The choice
    Every price is the route total for all travelers, never a per⁠-⁠person figure multiplied up. Popular trips show no unsearched prices or counts, other departures show times only until searched, and a header badge counts which providers were checked.
    The trade-off
    Fewer numbers up front: a popular trip or another departure shows its price only after a search.