The Bearings That Learned to Hold My Coffee

QSO Radial VOR Dial Coasters
🎮 Play: Radial Rush

Thirty hobbies in, and I’ve noticed I keep coming back to the same geometric question: where did that signal come from? The coaster project is just the latest answer, but this time I want to document the bearing calculation properly—enough that someone else could build one.

From QSO to Azimuth

When I log an HF contact, the other station often gives me a Maidenhead grid locator. “DO33” means a rectangle roughly 2° longitude by 1° latitude, centred somewhere in the North Atlantic. The locator encodes position in alternating letter-number pairs: two letters for the field (18×18 divisions of the globe), two digits for the square (10×10 subdivisions), optionally two more letters for the subsquare.

To compute the bearing from my station (53.5°N, 113.5°W) to the centre of DO33:

import math

def grid_to_latlon(grid: str) -> tuple[float, float]:
    """Convert 4- or 6-char Maidenhead to lat/lon centroid."""
    grid = grid.upper()
    lon = (ord(grid[0]) - ord('A')) * 20 - 180
    lat = (ord(grid[1]) - ord('A')) * 10 - 90
    lon += int(grid[2]) * 2
    lat += int(grid[3]) * 1
    if len(grid) >= 6:
        lon += (ord(grid[4]) - ord('A')) * (2/24) + (1/24)
        lat += (ord(grid[5]) - ord('A')) * (1/24) + (1/48)
    else:
        lon += 1  # centre of square
        lat += 0.5
    return lat, lon

def azimuth(lat1, lon1, lat2, lon2) -> float:
    """Great-circle initial bearing, degrees clockwise from north."""
    lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
    dlon = lon2 - lon1
    x = math.sin(dlon) * math.cos(lat2)
    y = math.cos(lat1) * math.sin(lat2) - \
        math.sin(lat1) * math.cos(lat2) * math.cos(dlon)
    return (math.degrees(math.atan2(x, y)) + 360) % 360

That formula returns a true bearing. Alberta’s magnetic declination is currently about 14° east, so I subtract 14 from the result before plotting. If you skip this step, every radial will point roughly one hour off on a clock face—enough to make the coaster lie about where the world is.

I learned this the hard way during Star-Drift Compass Calibration Cards, when I spent twenty minutes blaming arithmetic for what a chain-link fence did to my reference bearing. Declination is a quiet error. It doesn’t announce itself.

Why VOR Geometry Works Here

A VOR dial is a compass rose with radials extending outward—lines of position an aircraft can track inbound or outbound. The same mental model applies to QSO bearings: each contact defines a direction from my antenna to the other station, and plotting several directions on one dial builds a picture of where the ionosphere cooperated that day.

The coaster is 80 mm diameter. Each radial is a raised rib 0.8 mm tall and 1.2 mm wide—any taller and a mug wobbles like a student pilot on crosswind day. The centre stays flat for stability.

def radial_endpoints(bearing_deg, center, radius):
    """Return (x1, y1, x2, y2) for a radial line."""
    angle = math.radians(bearing_deg)
    dx = radius * math.sin(angle)
    dy = radius * math.cos(angle)
    return (center[0], center[1], center[0] + dx, center[1] + dy)

I generate an STL by extruding each radial as a rectangular prism atop a flat disc. The slicer handles the rest. PLA at 0.2 mm layer height, matte top surface so the radials read visually instead of vanishing into gloss.

The Fabrication Constraints

PLA’s glass transition is around 55–60°C. A freshly boiled mug will soften the plastic and warp your geometry. These are desk coasters, not sauna equipment. If heat resistance matters, PETG survives higher temperatures but shows layer lines more harshly.

I label the underside with “MAG” and the declination value I used. Magnetic declination drifts ~0.1° per year here; a coaster printed in 2026 will be subtly wrong by 2030. Dating the print is museum-grade provenance for an object that holds coffee.

What This Actually Records

Five radials from a good afternoon: 48° (Northern Europe, long path), 103° (Japan, short path), 221° (South America), 305° (Alaska), 12° (Scandinavia). Each one is a conversation that actually happened—callsigns exchanged, signal reports logged, ionospheric geometry momentarily cooperating.

The coaster doesn’t record who I talked to, just where the RF went. It’s a compass rose drawn by propagation, and every new one is a reason to call CQ again.

There’s a satisfying lineage here. VOR identifiers are still transmitted in Morse code. QSO is a Q-code from 1910s maritime radio. QSL cards were literal postcards. Now I’m printing a navigation dial that holds a mug, and the FAA is decommissioning a third of VOR stations by 2030. The metaphor is fading from the sky just as I’m borrowing it for the desk.

When I made APRS Skytrace Sculptures, I was turning a flight path into a line in space. This is the same impulse flattened into two dimensions: data becomes something I can touch, and the desk quietly turns into a map.