Save up to 17% with yearly billing

See pricing

На маршруте

Готовые к запуску примеры работы с погодой на маршруте в пределах географического ограничивающего прямоугольника (bounding box): Ветер на высотах, PIREP и AIRMET/SIGMET. Сценарии с METAR/TAF для аэропортов см. в разделе В аэропорту.

#ВопросИспользуемые данные
7Ветер на крейсерском эшелоне FL180 в районе вылета из KJFK (региональный bbox в США)?Ветер на высотах
8Есть ли срочные донесения пилотов (UUA) вдоль маршрута за последние 3 часа?PIREP
9Действующие полигоны AIRMET, пересекающие bbox маршрута?AIRMET/SIGMET

Требования

Авторизация
x-api-key в каждом запросе (прямая подписка). Подробнее →

Установка

Приведённые ниже скрипты используют requests для вызовов API и rich для вывода в терминал. Установите их один раз:

pip install requests rich

7. Ветер на крейсерском эшелоне FL180 (ветер на высотах)

Вопрос: какой ближайший к FL180 уровень ветра на высотах в районе вылета из KJFK?

Ветер на высотах доступен только для США. Для длинных маршрутов запрашивайте региональные bbox вдоль участка над США (прямоугольник от побережья до побережья обрабатывается медленно и часто приводит к 503). Ниже — компактный прямоугольник вокруг Нью-Йорка.

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"

# Regional box around KJFK - reliable for FB Winds (US only)
NYC_BBOX = "38,-76,42,-71"
TARGET_ALT_FT = 18_000


def fetch_winds_aloft(bbox: str, forecast: int = 12) -> dict | None:
    r = requests.get(
        f"{BASE}/weather/winds-aloft",
        headers=HEADERS,
        params={"bbox": bbox, "forecast": forecast, "level": "low"},
        timeout=(10, 20),
    )
    if r.status_code in (404, 503):
        return None
    r.raise_for_status()
    return r.json()


def print_cruise_wind(bbox: str, label: str, target_ft: int = TARGET_ALT_FT) -> None:
    data = fetch_winds_aloft(bbox)
    if not data:
        console.print("[yellow]Winds aloft unavailable or no stations - retry or widen bbox.[/]")
        return
    if not data.get("stations"):
        console.print("[yellow]No FB winds stations in this bbox.[/]")
        return

    station = data["stations"][0]
    levels = station.get("winds") or []
    if not levels:
        console.print(f"[yellow]Station {station['station']} returned no wind levels.[/]")
        return

    best = min(levels, key=lambda w: abs(w["altitude_ft"] - target_ft))
    table = Table(title=f"Cruise wind near FL{target_ft // 100} - {label}")
    table.add_column("Field", style="cyan")
    table.add_column("Value", justify="right")
    table.add_row("Bbox", bbox)
    table.add_row("Station", station["station"])
    table.add_row("Level used", f"{best['altitude_ft']} ft MSL")
    table.add_row("Direction", f"{best.get('wind_direction') or '-'}° true")
    table.add_row("Speed", f"{best.get('wind_speed_kt') or '-'} kt")
    temp = best.get("temperature_c")
    table.add_row("Temperature", f"{temp} °C" if temp is not None else "not reported")
    table.add_row("Valid time", data.get("valid_time") or "-")
    console.print(table)


if __name__ == "__main__":
    print_cruise_wind(NYC_BBOX, "KJFK departure area")

Результат: таблица с ближайшим к целевой высоте уровнем ветра для первой станции в bbox.


8. Срочные PIREP вдоль маршрута

Вопрос: поступали ли за последние 3 часа в пределах bbox маршрута UUA (срочные) донесения пилотов?

PIREP поступают редко — пустой список здесь обычен. Если донесения есть, выводите UUA выше обычных донесений UA.

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"

KJFK = (40.6413, -73.7781)
KLAX = (33.9425, -118.4081)


def route_bbox(dep, arr, pad_deg: float = 2.0) -> str:
    dep_lat, dep_lon = dep
    arr_lat, arr_lon = arr
    return (
        f"{min(dep_lat, arr_lat) - pad_deg},{min(dep_lon, arr_lon) - pad_deg},"
        f"{max(dep_lat, arr_lat) + pad_deg},{max(dep_lon, arr_lon) + pad_deg}"
    )


def fetch_pireps(bbox: str, hours: int = 3) -> dict:
    r = requests.get(
        f"{BASE}/weather/pireps",
        headers=HEADERS,
        params={"bbox": bbox, "hours": hours},
        timeout=(10, 20),
    )
    r.raise_for_status()
    return r.json()


def print_urgent_pireps(dep, arr) -> None:
    bbox = route_bbox(dep, arr)
    data = fetch_pireps(bbox, hours=3)
    reports = data.get("reports") or []
    urgent = [r for r in reports if r.get("report_type") == "UUA"]
    routine = [r for r in reports if r.get("report_type") != "UUA"]

    if not reports:
        console.print(Panel(
            f"No PIREPs in bbox {bbox} (last 3 h).\n"
            "This is normal on quiet routes - not an error.",
            title="PIREPs - none filed",
            style="blue",
        ))
        return

    lines = []
    for r in urgent + routine[:5]:
        tag = "[red]UUA[/]" if r.get("report_type") == "UUA" else "UA"
        turb = r.get("turbulence") or "-"
        icing = r.get("icing") or "-"
        lines.append(f"{tag} {r.get('location') or '?'} @ {r.get('altitude') or '?'} - turb: {turb}, ice: {icing}")

    console.print(Panel(
        "\n".join(lines),
        title=f"PIREPs in route bbox ({data['total']} total, {len(urgent)} urgent)",
    ))


if __name__ == "__main__":
    print_urgent_pireps(KJFK, KLAX)

Результат: панель, где сначала перечислены срочные PIREP, а затем до пяти обычных донесений; либо понятное сообщение «донесений нет».


9. Действующие AIRMET на маршруте

Вопрос: пересекают ли сейчас bbox маршрута какие-либо предупреждения AIRMET?

Используйте type=airmet для бюллетеней об IFR, турбулентности и обледенении в формате, привычном для службы диспетчерского обеспечения полётов. Если observation.coords присутствует, отрисуйте его на карте.

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"

KJFK = (40.6413, -73.7781)
KLAX = (33.9425, -118.4081)


def route_bbox(dep, arr, pad_deg: float = 2.0) -> str:
    dep_lat, dep_lon = dep
    arr_lat, arr_lon = arr
    return (
        f"{min(dep_lat, arr_lat) - pad_deg},{min(dep_lon, arr_lon) - pad_deg},"
        f"{max(dep_lat, arr_lat) + pad_deg},{max(dep_lon, arr_lon) + pad_deg}"
    )


def fetch_airsigmets(bbox: str, advisory_type: str = "airmet") -> dict:
    r = requests.get(
        f"{BASE}/weather/airsigmet",
        headers=HEADERS,
        params={"bbox": bbox, "type": advisory_type},
        timeout=(10, 25),
    )
    r.raise_for_status()
    return r.json()


def print_route_airsigmets(dep, arr) -> None:
    bbox = route_bbox(dep, arr)
    data = fetch_airsigmets(bbox)
    reports = data.get("reports") or []

    if not reports:
        console.print(f"[green]No active AIRMETs intersect bbox {bbox} - fair weather or outside advisory area.[/]")
        return

    table = Table(title=f"Active AIRMETs - {len(reports)} in bbox")
    table.add_column("Type")
    table.add_column("Valid")
    table.add_column("Hazard")
    table.add_column("Polygon points")

    for r in reports:
        obs = r.get("observation") or {}
        hazard = obs.get("type") or r.get("report_type") or "-"
        floor = obs.get("floor") or "-"
        ceiling = obs.get("ceiling") or "-"
        coords = obs.get("coords") or []
        valid = f"{r.get('start_time') or '?'} → {r.get('end_time') or '?'}"
        table.add_row(
            r.get("report_type") or "AIRMET",
            valid,
            f"{hazard} ({floor}–{ceiling})",
            str(len(coords)),
        )

    console.print(table)
    console.print("[dim]Render observation.coords as map polygons; hide expired advisories past end_time.[/dim]")


if __name__ == "__main__":
    print_route_airsigmets(KJFK, KLAX)

Результат: таблица действующих AIRMET с окном действия, типом явления и числом вершин полигона.


См. также: METAR · TAF · Ветер на высотах · PIREP · AIRMET/SIGMET · Обработка ошибок · Кэширование и мониторинг