Stefan Koch
Stefan Koch
Live Flight Data Step by Step - Part 15 - Holdings, go-arounds and echoes

Live Flight Data Step by Step - Part 15 - Holdings, go-arounds and echoes

Stefan Koch Stefan Koch
· · 11 min read

In part 10 I found 37 minutes on Tuesday afternoon, 14:14 to 14:51, in which no airliner took off at Zurich. The landings went on. This part asks what the arriving aircraft did in that time. Did they have to wait in a holding pattern? Did any of them go around?

The data is the same as in parts 4 to 14: the Swiss feed from 20 October, 14:00 to 21 October, 11:59 Swiss time, one snapshot about every 35 seconds. All times are Swiss time.

Two ways to wait in the air

  • Holding. The aircraft flies a racetrack over a fixed point: a 180° turn, a straight leg of one minute, another 180° turn, one minute back. Standard holdings turn right (ICAO Doc 8168, Vol. I). The official Swiss charts aren’t openly available. According to a chart for flight simulation, Zurich has holdings at three points, used from 7,000 ft upwards. Aircraft wait in them stacked above each other until approach control calls them in.
  • Go-around. The crew breaks off the approach or the landing and climbs away (SKYbrary), for example because the runway isn’t clear or the approach isn’t stable. Then it flies a new approach. A study with ADS-B data of 20 large European airports found 1.5 to 6 go-arounds per 1,000 landings (Figuet et al. 2023).

Both should be visible in the positions alone. A holding is a heading that turns through 360° or more at roughly the same altitude. A go-around is an aircraft that comes down close to the runway and climbs again without touching the ground.

During the 37 minutes

First the direct question. The course notebook has two simple detectors:

  • Holding: within 20 minutes at least eight positions, turned through at least 360° in total, altitude within 2,000 ft, all positions within 15 km of the centre, at least 250 km/h. It looks only between 6,500 and 30,000 ft and within 80 km of Zurich.
  • Go-around: the lowest airborne position within 8 km of the airport and less than 2,500 ft above it. In the five minutes before, the aircraft was at least 300 ft higher and in the air. In the five minutes after, it climbs more than 500 ft before it touches the ground.

For the afternoon of the outage, both find nothing. No holding, no go-around.

No holding doesn’t have to mean no delay. Controllers can also make an aircraft lose time by sending it on a detour or slowing it down. So I measured one more thing: the minutes from the moment an arriving aircraft enters a circle of 50 km around the airport until it lands.

# first position within 4 km of the airport and below 1,800 ft = landing;
# a gap of more than 30 minutes between two such positions = a new landing of the same aircraft
cand = near[(near.dist_km < 4) & (near.altitude_ft < 1800)
            & near.flight_phase.isin(["descending", "on ground"])].sort_values(["icao24", "snapshot_time"])
cand["seg"] = (cand.groupby("icao24").snapshot_time.diff() > pd.Timedelta(minutes=30)).groupby(cand.icao24).cumsum()
landings = cand.groupby(["icao24", "seg"]).snapshot_time.min()

For each landing, I go back through the aircraft’s airborne positions within 50 km, as long as there’s no gap of more than five minutes. The notebook counts only the first landing per aircraft. An aircraft that lands twice in my 22 hours – in the evening and again the next morning – would then be missing once. My version finds 220 landings. 210 of them I can follow from at least 45 km out.

That’s fewer than the real number: EUROCONTROL counted 795 and 793 flights on these days (part 14), about half of them arrivals. Close to the ground, the receivers lose many aircraft before they are below 1,800 ft.

Map of the area around Zurich airport with eight orange approach tracks. They come from the north, the south-west and the south, join north-west of the airport and end a few kilometres before runway 14

During the 37 minutes, 8 aircraft landed that I can follow this way. They needed 8 to 12 minutes from 50 km to the runway, 11.2 minutes in the median. Over all 210 landings, the median is 11.4 minutes.

Scatter plot: one dot per landing, landing time on the horizontal axis from Tuesday 14:00 to Wednesday 12:00, minutes from 50 km to the runway on the vertical axis. Most dots lie between 7 and 15 minutes, the median is 11.4. The 37 minutes on Tuesday afternoon are shaded; the dots in them lie between 8 and 12 minutes. The longest approaches, 19 to 26 minutes, are on Wednesday between 11:30 and 12:00

So during the outage, the arriving aircraft landed as on any other afternoon. Part 10 already showed that the landings didn’t stop. Now I can add that they weren’t delayed in the air either – at least not in the last 50 km.

The longest approaches of the whole window were somewhere else: on Wednesday between 11:30 and 12:00, 11 landings needed 19 minutes in the median, up to 26 minutes. At that time it rained (part 13), and since about 10:00 the aircraft had been landing on runway 28 instead of 14. Whether one of the two, or something else, made the approaches longer, I can’t tell from my data.

Six go-arounds that weren’t

Then I ran both detectors over the whole 22 hours. The holding detector found one aircraft, the go-around detector six.

Six go-arounds in about 350 arrivals would be more than the study above would lead me to expect, but not impossible. So I looked at each of them. Here’s one, SWR9G on Wednesday morning:

Two panels. Left: altitude over time for SWR9G. A steady descent from 6,300 ft at 09:57 to 1,425 ft at 10:04, just above the airport elevation. Then grey bars for positions on the ground, and in between three orange dots at 3,700, 2,950 and 2,725 ft. Right: the same positions on a map. The approach comes from the east to the airport, and the three orange dots lie exactly on the approach path, 4 to 13 km before the runway

The descent is clean. At 10:04 the aircraft is 1,425 ft high, almost on the runway. Then the feed reports it on the ground – and in between, three times, at 3,700, 2,950 and 2,725 ft, several kilometres east of the airport. My detector sees a climb of 2,275 ft and calls it a go-around.

On the map it’s clear what these positions are. They lie on the approach path, exactly where the aircraft was a few minutes earlier. The position at 10:05:13 is 630 m from the one at 10:01:08, and 125 ft lower. These positions are echoes of the approach.

All six “go-arounds” look like this. For every airborne position in the six minutes after the low point, I searched for the nearest position of the same aircraft in the ten minutes before. They are 0.1 to 1.7 km apart and differ by less than 350 ft – while the aircraft was really on the ground or close to it. Some of the echoes come with a speed of 5 to 45 km/h, the speed of an aircraft taxiing, at an altitude of several thousand feet.

The echoes aren’t rare. Of the 206 airline landings I can follow, 45 have at least one, and in 29 of them it’s 500 ft or more above the landing point – enough for my go-around detector. Where they come from, I don’t know. OpenSky delivers them with a current timestamp and under the aircraft’s own ICAO address. For three of them I checked whether another aircraft in the feed was at that position at the time: none was.

A filter against echoes

A real go-around climbs away over the runway and beyond. An echo jumps back to a place the aircraft has already been. So I drop every position after the low point that lies within 2 km and 500 ft of a position the same aircraft had at least one minute earlier:

def echoes(track, t, look_min=10, after_min=6, km=2.0, ft=500):
    before = track[track.snapshot_time.between(t - pd.Timedelta(minutes=look_min), t)]
    after = track[(track.snapshot_time > t) & (track.snapshot_time <= t + pd.Timedelta(minutes=after_min))
                  & ~track.on_ground.fillna(False)]
    out = []
    for i, p in after.iterrows():
        b = before[before.snapshot_time <= p.snapshot_time - pd.Timedelta(minutes=1)]
        d = haversine_km(b.latitude, b.longitude, p.latitude, p.longitude)
        if ((d < km) & ((b.altitude_ft - p.altitude_ft).abs() < ft)).any():
            out.append(i)
    return out

With this filter, the detector finds no go-around in the 22 hours. That fits the study: with 1.5 to 6 per 1,000 landings, I would expect zero to two among my roughly 350 arrivals.

My first filters were simpler and didn’t work. “Airborne, but slower than 100 km/h” catches the echoes with taxiing speed, but not those that come with the approach speed, like SWR9G at 10:05:13 with 294 km/h. And it would also remove helicopters. “A jump faster than 1,000 km/h” misses echoes that are close enough to the previous position. Only the comparison with the aircraft’s own track caught all six.

The course module enrichment/go_arounds.py has stricter thresholds – less than 1,500 ft above the airport, more than 800 ft of climb. And it counts a climb only while the aircraft is still nearest to the same airport. On the notebook’s run, it found no go-around at Zurich. But I don’t think it’s immune: run on my 22 hours without a METAR, it reports a go-around at Zurich at 11:54 on Wednesday, and others at the Dübendorf airfield, 10 km south-east – among them SWR9G and ELY347, which were landing at Zurich. Their echoes simply sat nearer to Dübendorf. I haven’t checked these one by one.

The one holding

The holding detector found ELY347, an El Al flight, on Wednesday at 10:02. At 7,050 ft, about 25 km north-east of the airport, it flew one loop in about four minutes, then continued its descent and landed on runway 28 at about 10:12.

That’s not a holding in the sense above: one loop, not a racetrack over a holding point, and in the middle of the descent. The course module doesn’t report it. It looks at all positions from 1,000 ft upwards, and within 20 minutes the aircraft descended by much more than 2,000 ft. The notebook’s detector only sees positions above 6,500 ft – there, the loop is almost all that’s left. What’s interesting is the timing. In my data, the last landing on runway 14 was at 09:52, the first on 28 at 09:59. ELY347 flew its loop a few minutes after the change. Whether that’s the reason, the positions don’t say.

What I take from this

The question about the 37 minutes has a short answer: the arrivals didn’t notice. No holding, no go-around, normal approach times. The Skyguide fault stopped the departures and nothing else that my data can see.

The longer lesson is about the data. A detector that finds something rare is the one that needs the closest look. My go-around detector worked correctly on its test profiles and still reported six events that weren’t there – because about one landing in five has positions that repeat the approach after touchdown. Without plotting each case, I would have written about six go-arounds in 22 hours.

What’s next

The next part is the last one of the series: a small dashboard from the pipeline, and a look back at what worked, what didn’t, and what 22 hours of a crowdsourced sample can and can’t tell.

Data source

Flight data: The OpenSky Network – Matthias Schäfer, Martin Strohmeier, Vincent Lenders, Ivan Martinovic and Matthias Wilhelm. “Bringing Up OpenSky: A Large-scale ADS-B Sensor Network for Research”. In Proceedings of the 13th IEEE/ACM International Symposium on Information Processing in Sensor Networks (IPSN), pages 83–94, April 2014. doi:10.1109/IPSN.2014.6846743

Municipality boundaries on the cover: Federal Statistical Office (BFS), generalised boundaries, as in part 12. Airport reference point: OurAirports, public domain.

Holdings and go-arounds

Related posts

Related Articles

Comments

Comments are powered by giscus. You need a GitHub account to comment.