# This trick only helps to trigger the proper Javascript library require queries.
from ipyleaflet import Map
Map()

Calibration flights

What is this plane doing?

This was my first reaction after hitting on the following trajectory during an analysis of approaches at Toulouse airport. After exchanges with ATC people, I learned that these trajectories are flown by small aircraft working at calibrating landing assistance systems, including ILS and VOR.

These trajectories mostly consist of many low passes over an airport and large circles or arcs of circle. A small sample of such trajectories is included in traffic.data.samples, and the following snippet of code will let you explore those before an attempt of explanation.

Select a different area/airport:

You may click on trajectories for more information.

A short introduction to radio-navigation

A number of navigational systems emerged in the second half of the 20th century, mainly based on VHF communications. Some of them were designed for surveillance, like Secondary surveillance radars; other systems also came to assist pilot in navigation and landing.

VOR (VHF Omnidirectional Range) ground stations send an omnidirectional master signal (on a predefined frequency determined for each station) and a second highly directional signal. Aircraft measure the phase difference between the two signals, which corresponds to the bearing from the station to the aircraft. [1]

Historically, airways were laid out between VORs, which stand at the intersections between those routes. Advances in GNSS (understand GPS) make these stations less necessary.

VOR stations often host a DME (Distance Measuring Equipment). The principle is similar to radar ranging, except the roles of the aircraft and of the ground station are reversed. The aircraft sends a signal to the DME; the DME repeats the same signal 50 μs after reception. When the aircraft receives a copy of the sent messages, it measures the time of travel to the DME, subtracts 50 μs and divides the results by 2: speed of light gives an estimation of the distance between the aircraft to the ground station.

ILS (Instrument Landing Systems) consists of two guidance systems: a lateral one (the LOC, for localizer) and a vertical one (the GS, for glide slope, also glide path). The localizer usually consists of several pairs of directional antennas placed beyond the departure end of the runway. [2]

Local authorities define very strict thresholds for accuracy: internal monitoring shall switch off the system if the accuracy of the signal is not appropriate. All radio-navigation beacons (including VOR, DME and ILS) are checked periodically by specially equipped aircraft. In particular, the VOR test consists of flying around the beacon in circles at defined distances and along several radials.

A basic analysis of VOR calibration trajectories

We can have a look at the first trajectory in the calibration dataset. The aircraft takes off from Ajaccio airport before flying concentric circles and radials. There must be a VOR around, we can search in the navaid database:

from traffic.data.samples.calibration import ajaccio
from traffic.data import navaids

navaids.extent(ajaccio).query('type == "VOR"')
name type latitude longitude altitude frequency magnetic_variation description
272196 AJO VOR 41.770528 8.774667 2142.0 114.80 2.0 AJACCIO VOR-DME
272565 BTA VOR 42.573583 9.474833 36.0 114.15 2.0 BASTIA PORETTA VOR-DME
273152 FGI VOR 41.502194 9.083417 144.0 116.70 2.0 FIGARI VOR-DME
275091 SME VOR 40.890056 9.501278 59.0 113.90 1.0 SMERALDA OLBIA VOR-DME

Next step is to compute for each point the distance and bearing from the VOR to each point of the trajectory. The parts of the trajectory that are of interest are the ones with little to no variation in the distance (circles) and in the bearing (radials) to the VOR.

Then, we can write a simple .query() followed by a .split() method to select all segments with a constant bearing and distance with respect to the selected VOR.

from functools import reduce
from operator import or_

vor = navaids.extent(ajaccio)['AJO']

ajaccio = (
    ajaccio.distance(vor)  # add a distance column (in nm) w.r.t the VOR
    .bearing(vor)  # add a bearing column w.r.t the VOR
    .assign(
        distance_diff=lambda df: df.distance.diff().abs(),  # large circles
        bearing_diff=lambda df: df.bearing.diff().abs(),  # long radials
    )
)

constant_distance = list(
    segment for segment in ajaccio.query('distance_diff < .02').split('1 min')
    if segment.longer_than('5 minutes')
)

# trick to display many trajectories
reduce(or_, constant_distance)

Flight

  • callsign: CALIBRA
  • aircraft: 39b415 · 🇫🇷 F-HNAV (BE20)
  • start: 2018-01-12 09:16:15+00:00
  • stop: 2018-01-12 09:36:35+00:00
  • duration: 0 days 00:20:20
  • sampling rate: 7 second(s)

Flight

  • callsign: CALIBRA
  • aircraft: 39b415 · 🇫🇷 F-HNAV (BE20)
  • start: 2018-01-12 09:49:05+00:00
  • stop: 2018-01-12 10:03:30+00:00
  • duration: 0 days 00:14:25
  • sampling rate: 6 second(s)

We have all we need to enhance the interesting parts of the trajectory now:

import matplotlib.pyplot as plt

from cartes.crs import Lambert93, EuroPP
from cartes.utils.features import countries

from traffic.data import airports

point_params = dict(zorder=5, text_kw=dict(fontname="Ubuntu", fontsize=15))
box_params = dict(boxstyle="round", facecolor="lightpink", alpha=0.7, zorder=5)

with plt.style.context("traffic"):

    fig, ax = plt.subplots(subplot_kw=dict(projection=EuroPP()))

    ax.add_feature(countries())

    # airport information
    airports["LFKJ"].point.plot(ax, **point_params)

    # VOR information
    shift_vor = dict(units="dots", x=20, y=10)
    vor.plot(ax, marker="h", shift=shift_vor, **point_params)

    # full trajectory in dashed lines
    ajaccio.plot(ax, color="#aaaaaa", linestyle="--")

    # constant distance segments
    for segment in ajaccio.query("distance_diff < .02").split("1 minute"):
        if segment.longer_than("3 minutes"):
            segment.plot(ax, color="crimson")

            # an annotation with the radius of the circle
            distance_vor = segment.data.distance.mean()
            segment.at().plot(
                ax,
                alpha=0,  # We don't need the point, only the text
                text_kw=dict(s=f"{distance_vor:.1f} nm", bbox=box_params),
            )

    # constant bearing segments
    for segment in ajaccio.query("bearing_diff < .01").split("1 minute"):
        if segment.longer_than("3 minutes"):
            segment.plot(ax, color="forestgreen")

    ax.set_extent((7.6, 9.9, 41.2, 43.3))
    ax.spines["geo"].set_visible(False)
../_images/calibration_3_0.png

The following map displays the result of a similar processing on the other VOR calibration trajectories from the sample dataset. [3]

Select a different VOR:

Equipped aircraft for beacon calibration

This list only contains the equipped aircraft for the calibration in the sample dataset. Apart from F-HNAV, registration numbers were found on social networks. Two of the aircraft registrations were not in the provided database at the time of the writing, so we added them manually.

from traffic.core import Traffic
from traffic.data import aircraft
from traffic.data.samples import calibration

(
    Traffic.from_flights(
        getattr(calibration, name).assign(
            # create a flight_id which includes the date of the flight
            flight_id=lambda df: f"{name} ({df.timestamp.min():%Y-%m-%d})"
        )
        for name in calibration.__all__
    )
    .summary(["flight_id", "icao24", "start"])
    .eval()
    .merge(aircraft.data)
    .sort_values(["registration", "start"])
    .groupby(["registration", "typecode", "icao24"])
    .apply(lambda df: ", ".join(df.flight_id))
    .to_frame()
)
0
registration typecode icao24
9M-FCL LJ60 750093 kota_kinabalu (2017-03-08)
C-GFIO CRJ2 c052bb vancouver (2018-10-06)
C-GNVC CRJ2 c06921 montreal (2018-12-11)
D-CFMD B350 3cce6f vienna (2018-11-20), munich (2019-03-04)
F-HNAV BE20 39b415 toulouse (2017-06-16), ajaccio (2018-01-12), m...
G-GBAS DA62 4070f4 london_heathrow (2018-01-12), lisbon (2018-11-...
G-TACN DA62 4076f1 cardiff (2019-02-15), london_gatwick (2019-02-28)
SE-LKY BE20 4ab179 bornholm (2018-11-26), kiruna (2019-01-30)
VH-FIZ B350 7c1a89 noumea (2017-11-05), sydney (2018-11-10), pert...
YS-111-N BE20 0b206f guatemala (2018-03-26), kingston (2018-06-26)
F-HNAV C-GFIO