Global Flight Routes

Real airline routes between the world’s 40 busiest airports, from the OpenFlights database.

Layers: ArcLayer + ScatterplotLayer + TextLayer | Map: Dark Matter

Code
#--------------------------------------------------------------------------------# CSV parser for OpenFlights format (handles quoted fields)
function parse_of_line(line)
    fields = String[]
    buf = IOBuffer()
    in_q = false
    for c in line
        if c == '"'
            in_q = !in_q
        elseif c == ',' && !in_q
            push!(fields, String(take!(buf)))
        else
            write(buf, c)
        end
    end
    push!(fields, String(take!(buf)))
    return fields
end

#--------------------------------------------------------------------------------# Download OpenFlights data
airports_file = Downloads.download(
    "https://raw.githubusercontent.com/jpatokal/openflights/master/data/airports.dat"
)
routes_file = Downloads.download(
    "https://raw.githubusercontent.com/jpatokal/openflights/master/data/routes.dat"
)

# Parse airports: IATA → (lat, lng)
airport_db = Dict{String, @NamedTuple{lat::Float64, lng::Float64}}()
for line in readlines(airports_file)
    f = parse_of_line(line)
    length(f) >= 8 || continue
    iata = f[5]
    (iata == "" || iata == "\\N" || length(iata) != 3) && continue
    lat = tryparse(Float64, f[7])
    lng = tryparse(Float64, f[8])
    (lat === nothing || lng === nothing) && continue
    airport_db[iata] = (lat = lat, lng = lng)
end

# Parse routes: collect direct-flight pairs (deduplicated)
route_set = Set{Tuple{String,String}}()
for line in readlines(routes_file)
    f = split(line, ',')
    length(f) >= 8 || continue
    src, dst, stops = f[3], f[5], f[8]
    stops == "0" || continue
    haskey(airport_db, src) && haskey(airport_db, dst) || continue
    push!(route_set, src < dst ? (src, dst) : (dst, src))
end

# Rank airports by connectivity, pick top 40
counts = Dict{String,Int}()
for (s, d) in route_set
    counts[s] = get(counts, s, 0) + 1
    counts[d] = get(counts, d, 0) + 1
end
top = Set(first.(sort(collect(counts), by = last, rev = true)[1:min(40, length(counts))]))

# Filter to routes between top airports
sel_routes = [(s, d) for (s, d) in route_set if s in top && d in top]
sel_routes = sel_routes[1:min(250, length(sel_routes))]

used = sort(collect(union(Set(first.(sel_routes)), Set(last.(sel_routes)))))

airports = (
    lng  = [airport_db[a].lng for a in used],
    lat  = [airport_db[a].lat for a in used],
    name = used,
)
routes = (
    src_lng = [airport_db[r[1]].lng for r in sel_routes],
    src_lat = [airport_db[r[1]].lat for r in sel_routes],
    tgt_lng = [airport_db[r[2]].lng for r in sel_routes],
    tgt_lat = [airport_db[r[2]].lat for r in sel_routes],
)

arcs = ArcLayer(
    data = routes,
    get_source_position = [:src_lng, :src_lat],
    get_target_position = [:tgt_lng, :tgt_lat],
    get_source_color = [0, 180, 235, 160],
    get_target_color = [160, 90, 255, 160],
    get_width = 1,
    great_circle = true,
)
dots = ScatterplotLayer(
    data = airports,
    get_position = [:lng, :lat],
    get_fill_color = [0, 210, 235, 220],
    get_radius = 50000,
    radius_min_pixels = 3,
)
labels = TextLayer(
    data = airports,
    get_position = [:lng, :lat],
    get_text = :name,
    get_size = 11,
    get_color = [255, 255, 255, 230],
    get_pixel_offset = [0, -14],
    background = true,
    get_background_color = [30, 30, 30, 180],
    background_padding = [4, 2, 4, 2],
    font_family = "Helvetica, Arial, sans-serif",
    font_weight = 700,
)

Deck(
    [arcs, dots, labels],
    initial_view_state = ViewState(longitude = 20.0, latitude = 15.0, zoom = 1.5, pitch = 15.0),
    map_style = "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
)