# Historische Tracks

Integration historischer ADS-B-Archive — Flugsuche, Wiedergabe von Tracks und Rekonstruktion des Flughafenverkehrs.



Lauffähige Beispiele für die Suche nach archivierten Flügen, das Abrufen vollständiger Flugdetails und die Abfrage des historischen Flughafenverkehrs. Aufbewahrungszeiträume und Tarifvoraussetzungen finden Sie in der vollständigen Referenz zu [historischem ADS-B](/docs/v31/historical-adsb).

| # | Frage                                                                                | Verwendete Daten                                               |
| - | ------------------------------------------------------------------------------------ | -------------------------------------------------------------- |
| 1 | Welche 5 Flüge hat das Luftfahrzeug mit dem Kennzeichen N636JB zuletzt durchgeführt? | `/ultra/history/flights`                                       |
| 2 | Vollständige Metadaten zum jüngsten Abflug von EGLL                                  | `/ultra/history/flights` → `/ultra/history/flight/{flight_id}` |
| 3 | Wie viele Ankünfte hatte KJFK in den letzten 24 Stunden?                             | `/ultra/history/airport/{icao}/traffic`                        |

<DocRequirements>
  <DocRequirementAuth />

  <DocRequirement label="Tarif" href="/docs/rate-limits#historical-adsb-path-tiers">
    `/ultra/history/...` erfordert Pro, Ultra oder Mega — `401`/`403` bedeutet, dass Ihr Tarif unterhalb dieser Schwelle liegt.
  </DocRequirement>
</DocRequirements>

### Installation [#install]

Die folgenden Skripte verwenden `requests` für API-Aufrufe und `rich` für die Terminalausgabe. Einmalig installieren:

```bash
pip install requests rich
```

***

### 1. Die letzten 5 Flüge nach Kennzeichen [#1-last-5-flights-by-tail-number]

**Frage:** Welche Routen ist N636JB zuletzt geflogen, und wie lange hat jeder Flug gedauert?

Geben Sie einen `registration`-Filter an und setzen Sie `limit=5`, um nur die jüngsten archivierten Flüge abzurufen.

```python
import os

import requests
from rich.console import Console
from rich.table import Table

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"

REGISTRATION = "N636JB"


def fetch_recent_flights(registration: str, limit: int = 5) -> list[dict] | None:
    r = requests.get(
        f"{BASE}/ultra/history/flights",
        headers=HEADERS,
        params={"registration": registration, "limit": limit},
        timeout=(10, 25),
    )
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return None
    r.raise_for_status()
    return r.json().get("flights", [])


def fmt_duration(seconds: int | None) -> str:
    if seconds is None:
        return "—"
    h, m = divmod(seconds // 60, 60)
    return f"{h}:{m:02d}"


def main() -> None:
    flights = fetch_recent_flights(REGISTRATION)
    if flights is None:
        return

    if not flights:
        console.print(f"[yellow]No archived flights found for {REGISTRATION}.[/yellow]")
        return

    table = Table(title=f"Recent Flights — {REGISTRATION}", border_style="cyan")
    table.add_column("Flight ID", style="dim")
    table.add_column("From")
    table.add_column("To")
    table.add_column("Departure (UTC)")
    table.add_column("Duration", justify="right")

    for f in flights:
        table.add_row(
            (f.get("flight_id") or "")[:8],
            f.get("departure_icao") or "—",
            f.get("arrival_icao") or "—",
            (f.get("departure_time") or "—")[:16],
            fmt_duration(f.get("duration_sec")),
        )

    console.print(table)


if __name__ == "__main__":
    main()
```

**Ausgabe:** Eine Tabelle der fünf jüngsten archivierten Flüge: Flug-ID (erste 8 Zeichen) | Von | Nach | Abflugzeit | Dauer (h:mm).

***

### 2. Kette Suche → Details: vollständige Metadaten zum jüngsten Abflug von EGLL [#2-search--detail-chain-full-metadata-for-the-most-recent-egll-departure]

**Frage:** Welches Luftfahrzeug, welche Route und welche Zeitangaben beschreiben den jüngsten Flug, der in London Heathrow gestartet ist?

Zweistufige Kette: Suchen Sie den letzten Abflug von EGLL, um eine `flight_id` zu erhalten, und rufen Sie anschließend den vollständigen Detaildatensatz einschließlich Flugzeugtyp und GPS-Spoofing-Kennzeichnung ab.

```python
import os

import requests
from rich.console import Console
from rich.panel import Panel

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"

DEPARTURE_ICAO = "EGLL"


def handle_plan_gate(r: requests.Response) -> bool:
    """Return True if request was blocked by plan gating."""
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return True
    return False


def fetch_latest_departure(departure_icao: str) -> str | None:
    r = requests.get(
        f"{BASE}/ultra/history/flights",
        headers=HEADERS,
        params={"departure_icao": departure_icao, "limit": 1},
        timeout=(10, 25),
    )
    if handle_plan_gate(r):
        return None
    r.raise_for_status()
    flights = r.json().get("flights", [])
    if not flights:
        return None
    return flights[0]["flight_id"]


def fetch_flight_detail(flight_id: str) -> dict | None:
    r = requests.get(
        f"{BASE}/ultra/history/flight/{flight_id}",
        headers=HEADERS,
        timeout=(10, 25),
    )
    if r.status_code == 404:
        return None
    if handle_plan_gate(r):
        return None
    r.raise_for_status()
    return r.json()


def fmt_duration(seconds: int | None) -> str:
    if seconds is None:
        return "—"
    h, m = divmod(seconds // 60, 60)
    return f"{h}h {m:02d}m"


def main() -> None:
    flight_id = fetch_latest_departure(DEPARTURE_ICAO)
    if not flight_id:
        console.print(f"[yellow]No recent departures found for {DEPARTURE_ICAO}.[/yellow]")
        return

    detail = fetch_flight_detail(flight_id)
    if detail is None:
        console.print(f"[yellow]Flight detail not available for ID {flight_id[:8]}.[/yellow]")
        return

    spoofing = detail.get("gps_spoofing_suspected")
    spoofing_str = "[red]YES — review track[/red]" if spoofing else "No"

    lines = [
        f"[bold]Registration:[/bold]    {detail.get('registration') or '—'}",
        f"[bold]Callsign:[/bold]        {detail.get('callsign') or '—'}",
        f"[bold]Aircraft Type:[/bold]   {detail.get('aircraft_type') or '—'}",
        f"[bold]Route:[/bold]           {detail.get('departure_icao') or '—'}  →  {detail.get('arrival_icao') or '—'}",
        f"[bold]Departure:[/bold]       {detail.get('departure_airport_name') or detail.get('departure_icao') or '—'}",
        f"[bold]Arrival:[/bold]         {detail.get('arrival_airport_name') or detail.get('arrival_icao') or '—'}",
        f"[bold]Dep time (UTC):[/bold]  {(detail.get('departure_time') or '—')[:16]}",
        f"[bold]Arr time (UTC):[/bold]  {(detail.get('arrival_time') or '—')[:16]}",
        f"[bold]Duration:[/bold]        {fmt_duration(detail.get('duration_sec'))}",
        f"[bold]Distance:[/bold]        {detail.get('distance_nm') or '—'} nm",
        f"[bold]GPS Spoofing:[/bold]    {spoofing_str}",
    ]
    console.print(
        Panel("\n".join(lines), title=f"Flight Detail — {flight_id[:8]}", border_style="cyan")
    )


if __name__ == "__main__":
    main()
```

**Ausgabe:** Ein Detailpanel mit Kennzeichen, Rufzeichen, Flugzeugtyp, vollständiger Route (Flughafennamen), Abflug- und Ankunftszeiten, Dauer, Entfernung und GPS-Spoofing-Kennzeichnung.

***

### 3. Ankünfte in KJFK in den letzten 24 Stunden [#3-kjfk-arrivals-in-the-last-24-hours]

**Frage:** Wie viele Flüge sind in den letzten 24 Stunden in JFK angekommen, und welches waren die ersten fünf?

Berechnen Sie `start` und `end` in UTC und fragen Sie den Endpoint für den Flughafenverkehr mit `direction=arr` ab. Beachten Sie, dass das verfügbare Zeitfenster der Aufbewahrungsgrenze Ihres Tarifs unterliegt (90 Tage beim Ultra-Pfad).

```python
import datetime
import os

import requests
from rich.console import Console
from rich.table import Table

console = Console()

HEADERS = {
    "X-RapidAPI-Key": os.getenv("RAPIDAPI_KEY", "YOUR_RAPIDAPI_KEY"),
    "X-RapidAPI-Host": "skylink-api.p.rapidapi.com",
}
BASE = "https://skylink-api.p.rapidapi.com"

AIRPORT = "KJFK"


def fetch_airport_traffic(
    icao: str,
    start: str,
    end: str,
    direction: str = "arr",
) -> dict | None:
    r = requests.get(
        f"{BASE}/ultra/history/airport/{icao}/traffic",
        headers=HEADERS,
        params={"start": start, "end": end, "direction": direction, "limit": 50},
        timeout=(10, 25),
    )
    if r.status_code in (401, 403):
        msg = r.json().get("message", "Plan not entitled.")
        console.print(
            f"[red]Access denied:[/red] {msg}\n"
            "[yellow]Upgrade to Pro or above for /ultra/history/... endpoints.[/yellow]"
        )
        return None
    r.raise_for_status()
    return r.json()


def main() -> None:
    now = datetime.datetime.now(datetime.timezone.utc)
    start = (now - datetime.timedelta(hours=24)).strftime("%Y-%m-%dT%H:%M:%SZ")
    end = now.strftime("%Y-%m-%dT%H:%M:%SZ")

    result = fetch_airport_traffic(AIRPORT, start=start, end=end, direction="arr")
    if result is None:
        return

    flights = result.get("flights", [])
    total = result.get("count", len(flights))

    console.print(
        f"[bold cyan]{AIRPORT}[/bold cyan] received "
        f"[bold]{total:,}[/bold] arrivals in the last 24 hours."
    )

    if not flights:
        console.print("[yellow]No individual flight records returned.[/yellow]")
        return

    table = Table(title=f"Sample Arrivals — {AIRPORT} (first 5)", border_style="cyan")
    table.add_column("Callsign")
    table.add_column("From")
    table.add_column("Arrival time (UTC)")

    for f in flights[:5]:
        table.add_row(
            f.get("callsign") or "—",
            f.get("departure_airport_icao") or "—",
            (f.get("landing_time") or "—")[:16],
        )

    console.print(table)
    console.print(
        "[dim]Window is subject to your plan's retention limit "
        "(Ultra path: up to 90 days).[/dim]"
    )


if __name__ == "__main__":
    main()
```

**Ausgabe:** Gesamtzahl der Ankünfte im 24-Stunden-Fenster, gefolgt von einer Beispieltabelle der ersten fünf Ankünfte: Rufzeichen | Von | Ankunftszeit.
