# На маршруте

Примеры интеграции погоды на маршруте — ветер на высотах, PIREP и AIRMET/SIGMET в пределах ограничивающего прямоугольника (bounding box).



Готовые к запуску примеры работы с погодой **на маршруте** в пределах географического &#x2A;*ограничивающего прямоугольника (bounding box)**: [Ветер на высотах](/docs/v31/winds-aloft), [PIREP](/docs/v31/pireps) и [AIRMET/SIGMET](/docs/v31/airsigmet). Сценарии с [METAR](https://en.wikipedia.org/wiki/METAR)/[TAF](https://en.wikipedia.org/wiki/Terminal_aerodrome_forecast) для аэропортов см. в разделе [В аэропорту](/docs/v31/weather-integration-airport).

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

<DocRequirements>
  <DocRequirementAuth />
</DocRequirements>

### Установка [#install]

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

```bash
pip install requests rich
```

***

### 7. Ветер на крейсерском эшелоне FL180 (ветер на высотах) [#7-cruise-wind-at-fl180-winds-aloft]

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

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

```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"

# 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 вдоль маршрута [#8-urgent-pireps-along-the-route]

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

[PIREP](https://www.aviationweather.gov/help/data/#pirep) поступают редко — пустой список здесь обычен. Если донесения есть, выводите `UUA` выше обычных донесений `UA`.

```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"

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](https://www.aviationweather.gov/help/data/#pirep), а затем до пяти обычных донесений; либо понятное сообщение «донесений нет».

***

### 9. Действующие AIRMET на маршруте [#9-active-airmets-on-the-route]

**Вопрос:*&#x2A; пересекают ли сейчас bbox маршрута какие-либо предупреждения &#x2A;*[AIRMET](https://www.aviationweather.gov/help/data/#airmet)**?

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

```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"

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](https://www.aviationweather.gov/help/data/#airmet) с окном действия, типом явления и числом вершин полигона.

***

См. также: [METAR](/docs/v31/weather-metar) · [TAF](/docs/v31/weather-taf) · [Ветер на высотах](/docs/v31/winds-aloft) · [PIREP](/docs/v31/pireps) · [AIRMET/SIGMET](/docs/v31/airsigmet) · [Обработка ошибок](/docs/v31/weather-integration-errors) · [Кэширование и мониторинг](/docs/v31/weather-integration-production)
