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 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)
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:Time, distance and bearing thresholds may need further ajustments for a proper picture. Note the kiruna14 seems to circle around a position that is not referenced in the database. Any help or insight welcome!
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.
Ever seen a Runway Calibration ? @Beechcraft #B300 VH-FIZ was out calibrating RWY16L @SydneyAirport this morning, making a number of approaches to calibrate and certify ILS precision 💯. The orbits in the track are done to fit in with regular arrivals and departures ✈️💙📷 pic.twitter.com/CRkXfsscHa
— 16Right Media (@www16Right) April 8, 2018
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) |